/*- * Copyright (c) 2001 Jordan DeLong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "wm.h" /* our handler table */ typedef void (*event_handler_t)(XEvent *event); static event_handler_t handlers[LASTEvent]; /* handler for DestroyNotify events */ static void event_destroy_notify(XEvent *event) { XDestroyWindowEvent *e = &event->xdestroywindow; client_t *client; if (XFindContext(display, e->window, client_context, (XPointer *) &client)) return; if (e->window != client->window) return; if (!client->flags.internal) XRemoveFromSaveSet(display, client->window); plugin_window_death(client); client_rm(client); } /* handler for ConfigureRequest */ static void event_configure_request(XEvent *event) { XConfigureRequestEvent *e = &event->xconfigurerequest; XWindowChanges wc; client_t *client; /* now we only update the structure if the window has one */ if (XFindContext(display, e->window, client_context, (XPointer *) &client)) client = NULL; /* * we need to respond to ConfigureRequests of windows that * haven't yet been mapped, and thus don't have client_t structs * in our list. */ if (!client) { wc.x = e->x; wc.y = e->y; wc.width = e->width; wc.height = e->height; wc.border_width = e->border_width; wc.stack_mode = e->detail; XConfigureWindow(display, e->window, e->value_mask, &wc); return; } /* the event should be for the client's window */ assert(e->window == client->window); /* * now for managed windows we do stuff to our managed * client structure. */ if (e->value_mask & CWX) client->x = e->x; if (e->value_mask & CWY) client->y = e->y; if (e->value_mask & CWWidth) client->width = e->width; if (e->value_mask & CWHeight) client->height = e->height; /* * when a client moves itself, it may need * to change workspaces */ client_gravitate(client); client_sizeframe(client); action_send_config(client); workspace_add_bypos(client->screen->desktop, client); plugin_geometry_change(client); } /* handler for MapRequest events */ static void event_map_request(XEvent *event) { XMapRequestEvent *e = &event->xmaprequest; screen_t *screen; client_t *client; /* * for some reason some broken clients (netscape) seem to do more * than one map request on top window..., so make sure this window * isn't already managed. */ if (!XFindContext(display, e->window, client_context, (XPointer *) &client)) return; if (XFindContext(display, e->parent, root_context, (XPointer *) &screen)) return; /* * give plugins a chance to handle this window exclusively, * if plugin_map_request returns PLUGIN_USING we don't managed * in with client_add. */ if (plugin_map_request(screen, e)) return; client = client_add(screen, e->window, NULL, NULL); if (!client) return; client_map(client, 1); } /* handler for MapNotify events */ static void event_map_notify(XEvent *event) { XMapEvent *e = &event->xmap; client_t *client; /* * look for the client context we created for it in event_map_request; * and only continue if this is a notify from the client->window; if * it's for the frame we don't care here. */ if (XFindContext(display, e->window, client_context, (XPointer *) &client)) return; if (e->window != client->window) return; /* give focus to new windows (if the options say to) */ if (options.focus_new && client->workspace) focus_setfocused(client); } /* handler for UnmapNotify events */ static void event_unmap_notify(XEvent *event) { XUnmapEvent *e = &event->xunmap; client_t *client; /* * handle unmap notify events on managed windows; we only are * interested in the unmapnotify events generated when the * client window (not the frame) unmaps; because that signals * that the client should be unmanaged. the only exception * is when we get unmap events from a reparent (see below). */ if (XFindContext(display, e->window, client_context, (XPointer *) &client)) return; if (e->window != client->window) return; /* * see if the event came from reparenting * (see screen.c, manage_existing_windows) */ if (client->flags.unmap_from_reparent) { client_map(client, 0); client->flags.unmap_from_reparent = 0; return; } /* * get rid of our client structure, reparent it to the root, * and remove from our saveset */ if (client->state == NormalState) XUnmapWindow(display, client->frame); plugin_window_death(client); client_setstate(client, WithdrawnState); if (!client->flags.internal) XRemoveFromSaveSet(display, client->window); XReparentWindow(display, client->window, client->screen->root, client->x, client->y); client_rm(client); } /* handler for ButtonPress events */ static void event_button_press(XEvent *event) { XButtonEvent *e = &event->xbutton; client_t *client; screen_t *screen; decor_t *decor; /* * if the click doesn't have a client context, check if it is * a root window click; in which case notify plugins. */ if (XFindContext(display, e->window, client_context, (XPointer *) &client)) { if (!XFindContext(display, e->window, root_context, (XPointer *) &screen)) plugin_root_button(screen, e); return; } /* * handle button presses on client frame window (for mouse_modifier * grabs), the client window to do focusing in click focus mode, * or client decoration. */ if (e->window == client->frame) { if (options.focus == FOCUS_CLICK) focus_setfocused(client); /* * either do move/resize for mouse_modifier grabs, or in * click focus mode we will get events for initial clicks * in the click->frame to do a raise. * * XXX: the buttons that do which for mouse_modifier should * be configurable. */ if ((e->state & ~(numlock_mask | scroll_mask | LockMask)) == options.mouse_modifier) { if (e->button == Button3) action_resize(client); else action_move(client); } else { XAllowEvents(display, ReplayPointer, CurrentTime); stacking_raise(client); } } else if (!XFindContext(display, e->window, decor_context, (XPointer *) &decor)) { if (options.focus == FOCUS_CLICK) focus_setfocused(client); decor_handlepress(client, decor, e); } } /* handler for ButtonRelease events */ static void event_button_release(XEvent *event) { XButtonEvent *e = &event->xbutton; screen_t *screen; /* * the only clicks we are concerned with are clicks on * a managed root window. */ if (XFindContext(display, e->window, root_context, (XPointer *) &screen)) return; plugin_root_button(screen, e); /* * switch workspace when clicking on the edges of the * screen. * * XXX: this should likely be removed at some point... */ if (e->button == Button1) { if (e->y_root == 0) { if (workspace_viewport_move(screen, screen->desktop, 0, -1)) XWarpPointer(display, None, screen->root, 0, 0, 1, 1, e->x_root, screen->height - 1); } else if (e->y_root == screen->height - 1) { if (workspace_viewport_move(screen, screen->desktop, 0, 1)) XWarpPointer(display, None, screen->root, 0, 0, 1, 1, e->x_root, 0); } else if (e->x_root == 0) { if (workspace_viewport_move(screen, screen->desktop, -1, 0)) XWarpPointer(display, None, screen->root, 0, 0, 1, 1, screen->width - 1, e->y_root); } else if (e->x_root == screen->width - 1) { if (workspace_viewport_move(screen, screen->desktop, 1, 0)) XWarpPointer(display, None, screen->root, 0, 0, 1, 1, 0, e->y_root); } } } /* handler for KeyPress events */ static void event_key_press(XEvent *event) { XKeyEvent *e = &event->xkey; screen_t *screen; /* * handle dispatching key bindings; get the screen on which * the event occured and pass it to the key_press handler. */ if (XFindContext(display, e->root, root_context, (XPointer *) &screen)) return; keys_press(screen, e); } /* handler for Expose events */ static void event_expose(XEvent *event) { XExposeEvent *e = &event->xexpose; client_t *client; decor_t *decor; /* * handle exposures for client decoration. */ if (XFindContext(display, e->window, client_context, (XPointer *) &client)) return; if (XFindContext(display, e->window, decor_context, (XPointer *) &decor)) return; decor_expose(client, decor, e); } /* handler for ClientMessage */ static void event_client_message(XEvent *event) { XClientMessageEvent *e = &event->xclient; client_t *client; if (XFindContext(display, e->window, client_context, (XPointer *) &client)) return; if (e->window != client->window) return; /* * currently, only handle state change requests * for iconification. */ if (e->message_type == WM_CHANGE_STATE && e->format == 32 && e->data.l[0] == IconicState && client->state != IconicState) action_iconify(client); } /* handler for PropertyNotify */ static void event_property_notify(XEvent *event) { XPropertyEvent *e = &event->xproperty; client_t *client; long supplied; #ifdef I18N XTextProperty text_prop; char **cl; int n; #endif /* get associated client, the event must be for it's window */ if (XFindContext(display, e->window, client_context, (XPointer *) &client)) return; assert(e->window == client->window); /* * notify plugins about the property event; plugins can use this * be aware of client title changes, or clients changing hinting * properties. if a plugin is using the property change, and is * certain noone else will be (i.e. a specific windowmanager hint * protocol) it may return PLUGIN_USING, in which case we don't * need to do anything else for this event. */ if (plugin_property_notify(client, e) == PLUGIN_USING) return; /* * support changing the normal hints for a managed client * window, and changing the client store name. */ if (e->state == PropertyNewValue) switch (e->atom) { case XA_WM_NAME: if (client->store_name) XFree(client->store_name); #ifdef I18N if (XGetWMName(display, client->window, &text_prop) != 0) { if (text_prop.encoding == XA_STRING) { client->store_name = (char *)text_prop.value; } else { XmbTextPropertyToTextList(display, &text_prop, &cl, &n); if (cl) { client->store_name = strdup(cl[0]); XFreeStringList(cl); } else { client->store_name = "NoName"; } } } else { client->store_name = "NoName"; } #else XFetchName(display, client->window, &client->store_name); #endif /* * XXX: need a callback to let plugins know */ if (client->state != IconicState) decor_titlechange(client); break; case XA_WM_NORMAL_HINTS: XGetWMNormalHints(display, client->window, &client->normalhints, &supplied); break; } } /* handler for EnterNotify */ static void event_enter_notify(XEvent *event) { XCrossingEvent *e = &event->xcrossing; client_t *client; /* * we should only get this event for sloppy/pointer focusing * modes; when entering the frame window in those modes, we * set the focus to that client. */ if (XFindContext(display, e->window, client_context, (XPointer *) &client)) return; if (e->window != client->frame) return; focus_setfocused(client); } /* handler for LeaveNotify */ static void event_leave_notify(XEvent *event) { XCrossingEvent *e = &event->xcrossing; client_t *client; /* * we should only recieve this event for pointer focusing * mode; when leaving a window in pointer focus, the window * is unfocused. */ if (XFindContext(display, e->window, client_context, (XPointer *) &client)) return; if (e->window != client->frame) return; /* * we check if the client has a workspace first, because we will get * a leave notify if the user iconifies a window, and the window * will have left it's workspace, and thus already be unfocused. */ if (client->workspace) focus_unfocus(client); } /* handler for ShapeNotify */ static void event_shape_notify(XEvent *event) { client_t *client; /* * when a client shapes it's window, we need to shape the frame * window accordingly. */ if (XFindContext(display, event->xany.window, client_context, (XPointer *) &client)) return; if (event->xany.window != client->window) return; client_shape(client); } /* * set up the table of event handlers; NULL out the events * that we don't handle. */ static void event_init() { int i; for (i = 0; i < LASTEvent; i++) handlers[i] = NULL; handlers[DestroyNotify] = event_destroy_notify; handlers[ConfigureRequest] = event_configure_request; handlers[MapRequest] = event_map_request; handlers[MapNotify] = event_map_notify; handlers[UnmapNotify] = event_unmap_notify; handlers[ButtonPress] = event_button_press; handlers[ButtonRelease] = event_button_release; handlers[KeyPress] = event_key_press; handlers[Expose] = event_expose; handlers[ClientMessage] = event_client_message; handlers[PropertyNotify] = event_property_notify; handlers[EnterNotify] = event_enter_notify; handlers[LeaveNotify] = event_leave_notify; } /* * handle an X event; this is exported to the world to allow pieces * of code that have their own internal event loops (for instance, * window movement) to call into the main handlers for certain * events (such as exposure events). */ void event_handle(XEvent *e) { plugin_t *plugin; /* * check event for plugin association; these events * are dispatched to a plugin handler instead of getting * handled here. */ if (!XFindContext(display, e->xany.window, plugin_context, (XPointer *) &plugin)) { plugin_handle_event(plugin, e); return; } /* * if a handler exists in the table of event handlers, * dispatch the event to it. */ if (e->type == shape_base + ShapeNotify) event_shape_notify(e); else if (handlers[e->type] != NULL) handlers[e->type](e); } /* * main loop for X event processing; when this routine * exits the window manager will die. */ void event_loop() { XEvent event; event_init(); while (1) { XNextEvent(display, &event); event_handle(&event); /* * if something sets restart_flag, we leave this function * and let main() take the appropriate action. */ if (restart_flag) return; } }