diff --git a/src/WindowMaker.h b/src/WindowMaker.h index 3c2869c6..606298d1 100644 --- a/src/WindowMaker.h +++ b/src/WindowMaker.h @@ -463,6 +463,7 @@ extern struct WPreferences { char show_clip_title; struct { + unsigned int replace:1; /* replace existing window manager */ unsigned int nodock:1; /* don't display the dock */ unsigned int noclip:1; /* don't display the clip */ unsigned int clip_merged_in_dock:1; /* disable clip, switch workspaces with dock */ diff --git a/src/event.c b/src/event.c index dbb927bd..1e100f77 100644 --- a/src/event.c +++ b/src/event.c @@ -103,6 +103,7 @@ static void handleFocusIn(XEvent *event); static void handleMotionNotify(XEvent *event); static void handleVisibilityNotify(XEvent *event); static void handle_inotify_events(void); +static void handle_selection_clear(XSelectionClearEvent *event); static void wdelete_death_handler(WMagicNumber id); @@ -274,6 +275,10 @@ void DispatchEvent(XEvent * event) #endif break; + case SelectionClear: + handle_selection_clear(&event->xselectionclear); + break; + default: handleExtensions(event); break; @@ -1886,3 +1891,17 @@ static void handleVisibilityNotify(XEvent * event) return; wwin->flags.obscured = (event->xvisibility.state == VisibilityFullyObscured); } + +static void handle_selection_clear(XSelectionClearEvent *event) +{ + WScreen *scr = wScreenForWindow(event->window); + + if (!scr) + return; + + if (event->selection != scr->sn_atom) + return; + + wmessage(_("another window manager is replacing us!")); + Shutdown(WSExitMode); +} diff --git a/src/main.c b/src/main.c index 297f3ab6..b68c5b6f 100644 --- a/src/main.c +++ b/src/main.c @@ -435,6 +435,7 @@ static void print_help(void) puts(_("The Window Maker window manager for the X window system")); puts(""); puts(_(" -display host:dpy display to use")); + puts(_(" --replace replace running window manager")); puts(_(" --no-dock do not open the application Dock")); puts(_(" --no-clip do not open the workspace Clip")); puts(_(" --no-autolaunch do not autolaunch applications")); @@ -651,6 +652,8 @@ static int real_main(int argc, char **argv) wPreferences.flags.noclip = 1; } else if (strcmp(argv[i], "-nodrawer") == 0 || strcmp(argv[i], "--no-drawer") == 0) { wPreferences.flags.nodrawer = 1; + } else if (strcmp(argv[i], "-replace") == 0 || strcmp(argv[i], "--replace") == 0) { + wPreferences.flags.replace = 1; } else if (strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) { printf("Window Maker %s\n", VERSION); exit(0); diff --git a/src/screen.c b/src/screen.c index 10a1fc95..1f246c45 100644 --- a/src/screen.c +++ b/src/screen.c @@ -66,6 +66,8 @@ |SubstructureRedirectMask|ButtonPressMask|ButtonReleaseMask\ |KeyPressMask|KeyReleaseMask) +#define REPLACE_WM_TIMEOUT 15 + #define STIPPLE_WIDTH 2 #define STIPPLE_HEIGHT 2 static char STIPPLE_DATA[] = { 0x02, 0x01 }; @@ -90,6 +92,106 @@ static void make_keys(void) dDrawers = WMCreatePLString("Drawers"); } +/* + * Support for ICCCM 2.0: Window Manager Replacement protocol + * See: http://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html + * + * Basically, user should be able to dynamically change its window manager; this is done + * cooperatively through a special Selection ("WM_Sn" where 'n' is the X screen number) + * + * This function does 2 things: + * - it checks if this selection is already owned, which means that another window + * manager is running. If it is the case and user specified '--replace' on the command + * line, then it asks the WM to shut down; + * - when ok, it sets the selection ownership to ourself, so another window manager + * may ask us to shut down (this is handled in "event.c") + */ +static Bool replace_existing_wm(WScreen *scr) +{ + char atomName[16]; + Window wm; + XSetWindowAttributes attribs; + XClientMessageEvent event; + unsigned long current_time; + int ret; + + /* Try to acquire the atom named WM_S */ + ret = snprintf(atomName, sizeof(atomName), "WM_S%d", scr->screen); + if (ret < 0 || ret == sizeof(atomName)) { + werror("out of memory trying to allocate window manager selection atom for screen %d", scr->screen); + return False; + } + + scr->sn_atom = XInternAtom(dpy, atomName, False); + if (! scr->sn_atom) + return False; + + /* Check if an existing window manager owns the selection */ + wm = XGetSelectionOwner(dpy, scr->sn_atom); + if (wm) { + if (!wPreferences.flags.replace) { + wmessage(_("another window manager is running")); + wwarning(_("use the --replace flag to replace it")); + return False; + } + + attribs.event_mask = StructureNotifyMask; + if (!XChangeWindowAttributes(dpy, wm, CWEventMask, &attribs)) + wm = None; + } + + /* for our window manager info notice board and the selection owner */ + scr->info_window = XCreateSimpleWindow(dpy, scr->root_win, 0, 0, 10, 10, 0, 0, 0); + + /* Try to acquire the selection */ + current_time = CurrentTime; + ret = XSetSelectionOwner(dpy, scr->sn_atom, scr->info_window, current_time); + if (ret == BadAtom || ret == BadWindow) + return False; + + /* Wait for other window manager to exit */ + if (wm) { + unsigned long wait = 0; + unsigned long timeout = REPLACE_WM_TIMEOUT * 1000000L; + XEvent event; + + while (wait < timeout) { + if (!(wait % 1000000)) + wmessage(_("waiting %lus for other window manager to exit"), (timeout - wait) / 1000000L); + + if (XCheckWindowEvent(dpy, wm, StructureNotifyMask, &event)) + if (event.type == DestroyNotify) + break; + + wusleep(100000); + wait += 100000; + } + + if (wait >= timeout) { + wwarning(_("other window manager hasn't exited!")); + return False; + } + + wmessage(_("replacing the other window manager")); + } + + if (XGetSelectionOwner(dpy, scr->sn_atom) != scr->info_window) + return False; + + event.type = ClientMessage; + event.message_type = scr->sn_atom; + event.format = 32; + event.data.l[0] = (long) current_time; + event.data.l[1] = (long) scr->sn_atom; + event.data.l[2] = (long) scr->info_window; + event.data.l[3] = (long) 0L; + event.data.l[4] = (long) 0L; + event.window = scr->root_win; + XSendEvent(dpy, scr->root_win, False, StructureNotifyMask, (XEvent *) &event); + + return True; +} + /* *---------------------------------------------------------------------- * alreadyRunningError-- @@ -523,6 +625,13 @@ WScreen *wScreenInit(int screen_number) CantManageScreen = 0; oldHandler = XSetErrorHandler(alreadyRunningError); + /* Do we need to replace an existing window manager? */ + if (!replace_existing_wm(scr)) { + XDestroyWindow(dpy, scr->info_window); + wfree(scr); + return NULL; + } + event_mask = EVENT_MASK; XSelectInput(dpy, scr->root_win, event_mask); @@ -616,11 +725,6 @@ WScreen *wScreenInit(int screen_number) /* create GCs with default values */ allocGCs(scr); - /* for our window manager info notice board. Need to - * create before reading the defaults, because it will be used there. - */ - scr->info_window = XCreateSimpleWindow(dpy, scr->root_win, 0, 0, 10, 10, 0, 0, 0); - /* read defaults for this screen */ wReadDefaults(scr, w_global.domain.wmaker->dictionary); diff --git a/src/screen.h b/src/screen.h index 8ffb6f9a..f3fc6fbf 100644 --- a/src/screen.h +++ b/src/screen.h @@ -69,6 +69,7 @@ typedef struct WDrawerChain { typedef struct _WScreen { int screen; /* screen number */ Window info_window; /* for our window manager info stuff */ + Atom sn_atom; /* window manager selection */ int scr_width; /* size of the screen */ int scr_height;