/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- event.c for the Openbox window manager Copyright (c) 2006 Mikael Magnusson Copyright (c) 2003-2007 Dana Jansens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. See the COPYING file for a copy of the GNU General Public License. */ #include "event.h" #include "debug.h" #include "window.h" #include "openbox.h" #include "dock.h" #include "actions.h" #include "client.h" #include "config.h" #include "screen.h" #include "frame.h" #include "grab.h" #include "menu.h" #include "prompt.h" #include "menuframe.h" #include "keyboard.h" #include "mouse.h" #include "focus.h" #include "focus_cycle.h" #include "moveresize.h" #include "group.h" #include "stacking.h" #include "ping.h" #include "obt/display.h" #include "obt/xqueue.h" #include "obt/prop.h" #include "obt/keyboard.h" #include #include #include #ifdef HAVE_SYS_SELECT_H # include #endif #ifdef HAVE_SIGNAL_H # include #endif #ifdef HAVE_UNISTD_H # include /* for usleep() */ #endif #ifdef USE_SM #include #endif typedef struct { gboolean ignored; } ObEventData; typedef struct { ObClient *client; Time time; gulong serial; } ObFocusDelayData; typedef struct { gulong start; /* inclusive */ gulong end; /* inclusive */ } ObSerialRange; static void event_process(const XEvent *e, gpointer data); static void event_handle_root(XEvent *e); static gboolean event_handle_menu_input(XEvent *e); static void event_handle_menu(ObMenuFrame *frame, XEvent *e); static gboolean event_handle_prompt(ObPrompt *p, XEvent *e); static void event_handle_dock(ObDock *s, XEvent *e); static void event_handle_dockapp(ObDockApp *app, XEvent *e); static void event_handle_client(ObClient *c, XEvent *e); static gboolean event_handle_user_input(ObClient *client, XEvent *e); static gboolean is_enter_focus_event_ignored(gulong serial); static void event_ignore_enter_range(gulong start, gulong end); static void focus_delay_dest(gpointer data); static void unfocus_delay_dest(gpointer data); static gboolean focus_delay_func(gpointer data); static gboolean unfocus_delay_func(gpointer data); static void focus_delay_client_dest(ObClient *client, gpointer data); Time event_last_user_time = CurrentTime; /*! The time of the current X event (if it had a timestamp) */ static Time event_curtime = CurrentTime; /*! The source time that started the current X event (user-provided, so not to be trusted) */ static Time event_sourcetime = CurrentTime; /*! The serial of the current X event */ static gulong event_curserial; static gboolean focus_left_screen = FALSE; static gboolean waiting_for_focusin = FALSE; /*! A list of ObSerialRanges which are to be ignored for mouse enter events */ static GSList *ignore_serials = NULL; static guint focus_delay_timeout_id = 0; static ObClient *focus_delay_timeout_client = NULL; static guint unfocus_delay_timeout_id = 0; static ObClient *unfocus_delay_timeout_client = NULL; #ifdef USE_SM static gboolean ice_handler(GIOChannel *source, GIOCondition cond, gpointer conn) { Bool b; IceProcessMessages(conn, NULL, &b); return TRUE; /* don't remove the event source */ } static void ice_watch(IceConn conn, IcePointer data, Bool opening, IcePointer *watch_data) { static guint id = 0; if (opening) { GIOChannel *ch; ch = g_io_channel_unix_new(IceConnectionNumber(conn)); id = g_io_add_watch(ch, G_IO_IN, ice_handler, conn); g_io_channel_unref(ch); } else if (id) { g_source_remove(id); id = 0; } } #endif void event_startup(gboolean reconfig) { if (reconfig) return; xqueue_add_callback(event_process, NULL); #ifdef USE_SM IceAddConnectionWatch(ice_watch, NULL); #endif client_add_destroy_notify(focus_delay_client_dest, NULL); } void event_shutdown(gboolean reconfig) { if (reconfig) return; #ifdef USE_SM IceRemoveConnectionWatch(ice_watch, NULL); #endif client_remove_destroy_notify(focus_delay_client_dest); } static Window event_get_window(XEvent *e) { Window window; /* pick a window */ switch (e->type) { case SelectionClear: window = obt_root(ob_screen); break; case CreateNotify: window = e->xcreatewindow.window; break; case MapRequest: window = e->xmaprequest.window; break; case MapNotify: window = e->xmap.window; break; case UnmapNotify: window = e->xunmap.window; break; case DestroyNotify: window = e->xdestroywindow.window; break; case ConfigureRequest: window = e->xconfigurerequest.window; break; case ConfigureNotify: window = e->xconfigure.window; break; default: #ifdef XKB if (obt_display_extension_xkb && e->type == obt_display_extension_xkb_basep) { switch (((XkbAnyEvent*)e)->xkb_type) { case XkbBellNotify: window = ((XkbBellNotifyEvent*)e)->window; default: window = None; } } else #endif #ifdef SYNC if (obt_display_extension_sync && e->type == obt_display_extension_sync_basep + XSyncAlarmNotify) { window = None; } else #endif window = e->xany.window; } return window; } static inline Time event_get_timestamp(const XEvent *e) { Time t = CurrentTime; /* grab the lasttime and hack up the state */ switch (e->type) { case ButtonPress: case ButtonRelease: t = e->xbutton.time; break; case KeyPress: t = e->xkey.time; break; case KeyRelease: t = e->xkey.time; break; case MotionNotify: t = e->xmotion.time; break; case PropertyNotify: t = e->xproperty.time; break; case EnterNotify: case LeaveNotify: t = e->xcrossing.time; break; default: #ifdef SYNC if (obt_display_extension_sync && e->type == obt_display_extension_sync_basep + XSyncAlarmNotify) { t = ((const XSyncAlarmNotifyEvent*)e)->time; } #endif /* if more event types are anticipated, get their timestamp explicitly */ break; } return t; } static void event_set_curtime(XEvent *e) { Time t = event_get_timestamp(e); /* watch that if we get an event earlier than the last specified user_time, which can happen if the clock goes backwards, we erase the last specified user_time */ if (t && event_last_user_time && event_time_after(event_last_user_time, t)) event_reset_user_time(); event_sourcetime = CurrentTime; event_curtime = t; } static void event_hack_mods(XEvent *e) { switch (e->type) { case ButtonPress: case ButtonRelease: e->xbutton.state = obt_keyboard_only_modmasks(e->xbutton.state); break; case KeyPress: break; case KeyRelease: break; case MotionNotify: e->xmotion.state = obt_keyboard_only_modmasks(e->xmotion.state); /* compress events */ { XEvent ce; ObtXQueueWindowType wt; wt.window = e->xmotion.window; wt.type = MotionNotify; while (xqueue_remove_local(&ce, xqueue_match_window_type, &wt)) { e->xmotion.x = ce.xmotion.x; e->xmotion.y = ce.xmotion.y; e->xmotion.x_root = ce.xmotion.x_root; e->xmotion.y_root = ce.xmotion.y_root; } } break; } } static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only) { gint mode = e->xfocus.mode; gint detail = e->xfocus.detail; Window win = e->xany.window; if (e->type == FocusIn) { /* These are ones we never want.. */ /* This means focus was given by a keyboard/mouse grab. */ if (mode == NotifyGrab) return FALSE; /* This means focus was given back from a keyboard/mouse grab. */ if (mode == NotifyUngrab) return FALSE; /* These are the ones we want.. */ if (win == obt_root(ob_screen)) { /* If looking for a focus in on a client, then always return FALSE for focus in's to the root window */ if (in_client_only) return FALSE; /* This means focus reverted off of a client */ else if (detail == NotifyPointerRoot || detail == NotifyDetailNone || detail == NotifyInferior || /* This means focus got here from another screen */ detail == NotifyNonlinear) return TRUE; else return FALSE; } /* It was on a client, was it a valid one? It's possible to get a FocusIn event for a client that was managed but has disappeared. */ if (in_client_only) { ObWindow *w = window_find(e->xfocus.window); if (!w || !WINDOW_IS_CLIENT(w)) return FALSE; } else { /* This means focus reverted to parent from the client (this happens often during iconify animation) */ if (detail == NotifyInferior) return TRUE; } /* This means focus moved from the root window to a client */ if (detail == NotifyVirtual) return TRUE; /* This means focus moved from one client to another */ if (detail == NotifyNonlinearVirtual) return TRUE; /* Otherwise.. */ return FALSE; } else { g_assert(e->type == FocusOut); /* These are ones we never want.. */ /* This means focus was taken by a keyboard/mouse grab. */ if (mode == NotifyGrab) return FALSE; /* This means focus was grabbed on a window and it was released. */ if (mode == NotifyUngrab) return FALSE; /* Focus left the root window revertedto state */ if (win == obt_root(ob_screen)) return FALSE; /* These are the ones we want.. */ /* This means focus moved from a client to the root window */ if (detail == NotifyVirtual) return TRUE; /* This means focus moved from one client to another */ if (detail == NotifyNonlinearVirtual) return TRUE; /* Otherwise.. */ return FALSE; } } static gboolean event_look_for_focusin(XEvent *e, gpointer data) { return e->type == FocusIn && wanted_focusevent(e, FALSE); } static gboolean event_look_for_focusin_client(XEvent *e, gpointer data) { return e->type == FocusIn && wanted_focusevent(e, TRUE); } static void print_focusevent(XEvent *e) { gint mode = e->xfocus.mode; gint detail = e->xfocus.detail; Window win = e->xany.window; const gchar *modestr, *detailstr; switch (mode) { case NotifyNormal: modestr="NotifyNormal"; break; case NotifyGrab: modestr="NotifyGrab"; break; case NotifyUngrab: modestr="NotifyUngrab"; break; case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break; default: g_assert_not_reached(); } switch (detail) { case NotifyAncestor: detailstr="NotifyAncestor"; break; case NotifyVirtual: detailstr="NotifyVirtual"; break; case NotifyInferior: detailstr="NotifyInferior"; break; case NotifyNonlinear: detailstr="NotifyNonlinear"; break; case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break; case NotifyPointer: detailstr="NotifyPointer"; break; case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break; case NotifyDetailNone: detailstr="NotifyDetailNone"; break; default: g_assert_not_reached(); } if (mode == NotifyGrab || mode == NotifyUngrab) return; g_assert(modestr); g_assert(detailstr); ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s", (e->xfocus.type == FocusIn ? "In" : "Out"), win, modestr, detailstr); } static void event_process(const XEvent *ec, gpointer data) { XEvent ee, *e; Window window; ObClient *client = NULL; ObDock *dock = NULL; ObDockApp *dockapp = NULL; ObWindow *obwin = NULL; ObMenuFrame *menu = NULL; ObPrompt *prompt = NULL; gboolean used; /* make a copy we can mangle */ ee = *ec; e = ⅇ window = event_get_window(e); if (window == obt_root(ob_screen)) /* don't do any lookups, waste of cpu */; else if ((obwin = window_find(window))) { switch (obwin->type) { case OB_WINDOW_CLASS_DOCK: dock = WINDOW_AS_DOCK(obwin); break; case OB_WINDOW_CLASS_CLIENT: client = WINDOW_AS_CLIENT(obwin); /* events on clients can be events on prompt windows too */ prompt = client->prompt; break; case OB_WINDOW_CLASS_MENUFRAME: menu = WINDOW_AS_MENUFRAME(obwin); break; case OB_WINDOW_CLASS_INTERNAL: /* we don't do anything with events directly on these windows */ break; case OB_WINDOW_CLASS_PROMPT: prompt = WINDOW_AS_PROMPT(obwin); break; } } else dockapp = dock_find_dockapp(window); event_set_curtime(e); event_curserial = e->xany.serial; event_hack_mods(e); /* deal with it in the kernel */ if (e->type == FocusIn) { print_focusevent(e); if (!wanted_focusevent(e, FALSE)) { if (waiting_for_focusin) { /* We were waiting for this FocusIn, since we got a FocusOut earlier, but it went to a window that isn't a client. */ ob_debug_type(OB_DEBUG_FOCUS, "Focus went to an unmanaged window 0x%x !", e->xfocus.window); focus_fallback(TRUE, config_focus_under_mouse, TRUE, TRUE); } } else if (client && e->xfocus.detail == NotifyInferior) { ob_debug_type(OB_DEBUG_FOCUS, "Focus went to the frame window"); focus_left_screen = FALSE; focus_fallback(FALSE, config_focus_under_mouse, TRUE, TRUE); /* We don't get a FocusOut for this case, because it's just moving from our Inferior up to us. This happens when iconifying a window with RevertToParent focus */ frame_adjust_focus(client->frame, FALSE); /* focus_set_client(NULL) has already been called */ } else if (e->xfocus.detail == NotifyPointerRoot || e->xfocus.detail == NotifyDetailNone || e->xfocus.detail == NotifyInferior || e->xfocus.detail == NotifyNonlinear) { ob_debug_type(OB_DEBUG_FOCUS, "Focus went to root or pointer root/none"); if (e->xfocus.detail == NotifyInferior || e->xfocus.detail == NotifyNonlinear) { focus_left_screen = FALSE; } /* If another FocusIn is in the queue then don't fallback yet. This fixes the fun case of: window map -> send focusin window unmap -> get focusout window map -> send focusin get first focus out -> fall back to something (new window hasn't received focus yet, so something else) -> send focusin which means the "something else" is the last thing to get a focusin sent to it, so the new window doesn't end up with focus. But if the other focus in is something like PointerRoot then we still want to fall back. */ if (xqueue_exists_local(event_look_for_focusin_client, NULL)) { ob_debug_type(OB_DEBUG_FOCUS, " but another FocusIn is coming"); } else { /* Focus has been reverted. FocusOut events come after UnmapNotify, so we don't need to worry about focusing an invalid window */ if (!focus_left_screen) focus_fallback(FALSE, config_focus_under_mouse, TRUE, TRUE); } } else if (!client) { ob_debug_type(OB_DEBUG_FOCUS, "Focus went to a window that is already gone"); /* If you send focus to a window and then it disappears, you can get the FocusIn for it, after it is unmanaged. Just wait for the next FocusOut/FocusIn pair, but make note that the window that was focused no longer is. */ focus_set_client(NULL); } else if (client != focus_client) { focus_left_screen = FALSE; frame_adjust_focus(client->frame, TRUE); focus_set_client(client); client_calc_layer(client); client_bring_helper_windows(client); } waiting_for_focusin = FALSE; } else if (e->type == FocusOut) { print_focusevent(e); if (!wanted_focusevent(e, FALSE)) ; /* skip this one */ /* Look for the followup FocusIn */ else if (!xqueue_exists_local(event_look_for_focusin, NULL)) { /* There is no FocusIn, this means focus went to a window that is not being managed, or a window on another screen. */ Window win, root; gint i; guint u; obt_display_ignore_errors(TRUE); if (XGetInputFocus(obt_display, &win, &i) && XGetGeometry(obt_display, win, &root, &i,&i,&u,&u,&u,&u) && root != obt_root(ob_screen)) { ob_debug_type(OB_DEBUG_FOCUS, "Focus went to another screen !"); focus_left_screen = TRUE; } else ob_debug_type(OB_DEBUG_FOCUS, "Focus went to a black hole !"); obt_display_ignore_errors(FALSE); /* nothing is focused */ focus_set_client(NULL); } else { /* Focus moved, so mark that we are waiting to process that FocusIn */ waiting_for_focusin = TRUE; /* nothing is focused right now, but will be again shortly */ focus_set_client(NULL); } if (client && client != focus_client) frame_adjust_focus(client->frame, FALSE); } else if (client) event_handle_client(client, e); else if (dockapp) event_handle_dockapp(dockapp, e); else if (dock) event_handle_dock(dock, e); else if (menu) event_handle_menu(menu, e); else if (window == obt_root(ob_screen)) event_handle_root(e); else if (e->type == MapRequest) window_manage(window); else if (e->type == MappingNotify) { /* keyboard layout changes for modifier mapping changes. reload the modifier map, and rebind all the key bindings as appropriate */ ob_debug("Keyboard map changed. Reloading keyboard bindings."); ob_set_state(OB_STATE_RECONFIGURING); obt_keyboard_reload(); keyboard_rebind(); ob_set_state(OB_STATE_RUNNING); } else if (e->type == ClientMessage) { /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for windows that are not managed yet. */ if (e->xclient.message_type == OBT_PROP_ATOM(NET_REQUEST_FRAME_EXTENTS)) { /* Pretend to manage the client, getting information used to determine its decorations */ ObClient *c = client_fake_manage(e->xclient.window); gulong vals[4]; /* set the frame extents on the window */ vals[0] = c->frame->size.left; vals[1] = c->frame->size.right; vals[2] = c->frame->size.top; vals[3] = c->frame->size.bottom; OBT_PROP_SETA32(e->xclient.window, NET_FRAME_EXTENTS, CARDINAL, vals, 4); /* Free the pretend client */ client_fake_unmanage(c); } } else if (e->type == ConfigureRequest) { /* unhandled configure requests must be used to configure the window directly */ XWindowChanges xwc; xwc.x = e->xconfigurerequest.x; xwc.y = e->xconfigurerequest.y; xwc.width = e->xconfigurerequest.width; xwc.height = e->xconfigurerequest.height; xwc.border_width = e->xconfigurerequest.border_width; xwc.sibling = e->xconfigurerequest.above; xwc.stack_mode = e->xconfigurerequest.detail; /* we are not to be held responsible if someone sends us an invalid request! */ obt_display_ignore_errors(TRUE); XConfigureWindow(obt_display, window, e->xconfigurerequest.value_mask, &xwc); obt_display_ignore_errors(FALSE); } #ifdef SYNC else if (obt_display_extension_sync && e->type == obt_display_extension_sync_basep + XSyncAlarmNotify) { XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e; if (se->alarm == moveresize_alarm && moveresize_in_progress) moveresize_event(e); } #endif if (e->type == ButtonPress || e->type == ButtonRelease) { ObWindow *w; static guint pressed = 0; event_sourcetime = event_curtime; /* If the button press was on some non-root window, or was physically on the root window... */ if (window != obt_root(ob_screen) || e->xbutton.subwindow == None || /* ...or if it is related to the last button press we handled... */ pressed == e->xbutton.button || /* ...or it if it was physically on an openbox internal window... */ ((w = window_find(e->xbutton.subwindow)) && (WINDOW_IS_INTERNAL(w) || WINDOW_IS_DOCK(w)))) /* ...then process the event, otherwise ignore it */ { used = event_handle_user_input(client, e); if (prompt && !used) used = event_handle_prompt(prompt, e); if (e->type == ButtonPress) pressed = e->xbutton.button; } } else if (e->type == KeyPress || e->type == KeyRelease || e->type == MotionNotify) { event_sourcetime = event_curtime; used = event_handle_user_input(client, e); if (prompt && !used) used = event_handle_prompt(prompt, e); } /* show any debug prompts that are queued */ ob_debug_show_prompts(); /* if something happens and it's not from an XEvent, then we don't know the time, so clear it here until the next event is handled */ event_curtime = event_sourcetime = CurrentTime; event_curserial = 0; } static void event_handle_root(XEvent *e) { Atom msgtype; switch(e->type) { case SelectionClear: ob_debug("Another WM has requested to replace us. Exiting."); ob_exit_replace(); break; case ClientMessage: if (e->xclient.format != 32) break; msgtype = e->xclient.message_type; if (msgtype == OBT_PROP_ATOM(NET_CURRENT_DESKTOP)) { guint d = e->xclient.data.l[0]; if (d < screen_num_desktops) { if (e->xclient.data.l[1] == 0) ob_debug_type(OB_DEBUG_APP_BUGS, "_NET_CURRENT_DESKTOP message is missing " "a timestamp"); else event_sourcetime = e->xclient.data.l[1]; screen_set_desktop(d, TRUE); } } else if (msgtype == OBT_PROP_ATOM(NET_NUMBER_OF_DESKTOPS)) { guint d = e->xclient.data.l[0]; if (d > 0 && d <= 1000) screen_set_num_desktops(d); } else if (msgtype == OBT_PROP_ATOM(NET_SHOWING_DESKTOP)) { screen_show_desktop(e->xclient.data.l[0] != 0, NULL); } else if (msgtype == OBT_PROP_ATOM(OB_CONTROL)) { ob_debug("OB_CONTROL: %d", e->xclient.data.l[0]); if (e->xclient.data.l[0] == 1) ob_reconfigure(); else if (e->xclient.data.l[0] == 2) ob_restart(); else if (e->xclient.data.l[0] == 3) ob_exit(0); } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) { if ((Atom)e->xclient.data.l[0] == OBT_PROP_ATOM(NET_WM_PING)) ping_got_pong(e->xclient.data.l[1]); } break; case PropertyNotify: if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_NAMES)) { ob_debug("UPDATE DESKTOP NAMES"); screen_update_desktop_names(); } else if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_LAYOUT)) screen_update_layout(); break; case ConfigureNotify: #ifdef XRANDR XRRUpdateConfiguration(e); #endif screen_resize(); break; default: ; } } void event_enter_client(ObClient *client) { g_assert(config_focus_follow); if (is_enter_focus_event_ignored(event_curserial)) { ob_debug_type(OB_DEBUG_FOCUS, "Ignoring enter event with serial %lu " "on client 0x%x", event_curserial, client->window); return; } ob_debug_type(OB_DEBUG_FOCUS, "using enter event with serial %lu " "on client 0x%x", event_curserial, client->window); if (client_enter_focusable(client) && client_can_focus(client)) { if (config_focus_delay) { ObFocusDelayData *data; if (focus_delay_timeout_id) g_source_remove(focus_delay_timeout_id); data = g_slice_new(ObFocusDelayData); data->client = client; data->time = event_time(); data->serial = event_curserial; focus_delay_timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT, config_focus_delay, focus_delay_func, data, focus_delay_dest); focus_delay_timeout_client = client; } else { ObFocusDelayData data; data.client = client; data.time = event_time(); data.serial = event_curserial; focus_delay_func(&data); } } } void event_leave_client(ObClient *client) { g_assert(config_focus_follow); if (is_enter_focus_event_ignored(event_curserial)) { ob_debug_type(OB_DEBUG_FOCUS, "Ignoring leave event with serial %lu\n" "on client 0x%x", event_curserial, client->window); return; } if (client == focus_client) { if (config_focus_delay) { ObFocusDelayData *data; if (unfocus_delay_timeout_id) g_source_remove(unfocus_delay_timeout_id); data = g_slice_new(ObFocusDelayData); data->client = client; data->time = event_time(); data->serial = event_curserial; unfocus_delay_timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT, config_focus_delay, unfocus_delay_func, data, unfocus_delay_dest); unfocus_delay_timeout_client = client; } else { ObFocusDelayData data; data.client = client; data.time = event_time(); data.serial = event_curserial; unfocus_delay_func(&data); } } } static gboolean *context_to_button(ObFrame *f, ObFrameContext con, gboolean press) { if (press) { switch (con) { case OB_FRAME_CONTEXT_MAXIMIZE: return &f->max_press; case OB_FRAME_CONTEXT_CLOSE: return &f->close_press; case OB_FRAME_CONTEXT_ICONIFY: return &f->iconify_press; case OB_FRAME_CONTEXT_ALLDESKTOPS: return &f->desk_press; case OB_FRAME_CONTEXT_SHADE: return &f->shade_press; default: return NULL; } } else { switch (con) { case OB_FRAME_CONTEXT_MAXIMIZE: return &f->max_hover; case OB_FRAME_CONTEXT_CLOSE: return &f->close_hover; case OB_FRAME_CONTEXT_ICONIFY: return &f->iconify_hover; case OB_FRAME_CONTEXT_ALLDESKTOPS: return &f->desk_hover; case OB_FRAME_CONTEXT_SHADE: return &f->shade_hover; default: return NULL; } } } static gboolean more_client_message_event(Window window, Atom msgtype) { ObtXQueueWindowMessage wm; wm.window = window; wm.message = msgtype; return xqueue_exists_local(xqueue_match_window_message, &wm); } struct ObSkipPropertyChange { Window window; Atom prop; }; static gboolean skip_property_change(XEvent *e, gpointer data) { const struct ObSkipPropertyChange s = *(struct ObSkipPropertyChange*)data; if (e->type == PropertyNotify && e->xproperty.window == s.window) { const Atom a = e->xproperty.atom; const Atom b = s.prop; /* these are all updated together */ if ((a == OBT_PROP_ATOM(NET_WM_NAME) || a == OBT_PROP_ATOM(WM_NAME) || a == OBT_PROP_ATOM(NET_WM_ICON_NAME) || a == OBT_PROP_ATOM(WM_ICON_NAME)) && (b == OBT_PROP_ATOM(NET_WM_NAME) || b == OBT_PROP_ATOM(WM_NAME) || b == OBT_PROP_ATOM(NET_WM_ICON_NAME) || b == OBT_PROP_ATOM(WM_ICON_NAME))) { return TRUE; } else if (a == b && a == OBT_PROP_ATOM(NET_WM_ICON)) return TRUE; } return FALSE; } static void event_handle_client(ObClient *client, XEvent *e) { Atom msgtype; ObFrameContext con; gboolean *but; static gint px = -1, py = -1; static guint pb = 0; static ObFrameContext pcon = OB_FRAME_CONTEXT_NONE; switch (e->type) { case ButtonPress: /* save where the press occured for the first button pressed */ if (!pb) { pb = e->xbutton.button; px = e->xbutton.x; py = e->xbutton.y; pcon = frame_context(client, e->xbutton.window, px, py); pcon = mouse_button_frame_context(pcon, e->xbutton.button, e->xbutton.state); } case ButtonRelease: /* Wheel buttons don't draw because they are an instant click, so it is a waste of resources to go drawing it. if the user is doing an interactive thing, or has a menu open then the mouse is grabbed (possibly) and if we get these events we don't want to deal with them */ if (!(e->xbutton.button == 4 || e->xbutton.button == 5) && !grab_on_keyboard()) { /* use where the press occured */ con = frame_context(client, e->xbutton.window, px, py); con = mouse_button_frame_context(con, e->xbutton.button, e->xbutton.state); /* button presses on CLIENT_CONTEXTs are not accompanied by a release because they are Replayed to the client */ if ((e->type == ButtonRelease || CLIENT_CONTEXT(con, client)) && e->xbutton.button == pb) pb = 0, px = py = -1, pcon = OB_FRAME_CONTEXT_NONE; but = context_to_button(client->frame, con, TRUE); if (but) { *but = (e->type == ButtonPress); frame_adjust_state(client->frame); } } break; case MotionNotify: /* when there is a grab on the pointer, we won't get enter/leave notifies, but we still get motion events */ if (grab_on_pointer()) break; con = frame_context(client, e->xmotion.window, e->xmotion.x, e->xmotion.y); switch (con) { case OB_FRAME_CONTEXT_TITLEBAR: case OB_FRAME_CONTEXT_TLCORNER: case OB_FRAME_CONTEXT_TRCORNER: /* we've left the button area inside the titlebar */ if (client->frame->max_hover || client->frame->desk_hover || client->frame->shade_hover || client->frame->iconify_hover || client->frame->close_hover) { client->frame->max_hover = client->frame->desk_hover = client->frame->shade_hover = client->frame->iconify_hover = client->frame->close_hover = FALSE; frame_adjust_state(client->frame); } break; default: but = context_to_button(client->frame, con, FALSE); if (but && !*but && !pb) { *but = TRUE; frame_adjust_state(client->frame); } break; } break; case LeaveNotify: con = frame_context(client, e->xcrossing.window, e->xcrossing.x, e->xcrossing.y); switch (con) { case OB_FRAME_CONTEXT_TITLEBAR: case OB_FRAME_CONTEXT_TLCORNER: case OB_FRAME_CONTEXT_TRCORNER: /* we've left the button area inside the titlebar */ client->frame->max_hover = client->frame->desk_hover = client->frame->shade_hover = client->frame->iconify_hover = client->frame->close_hover = FALSE; if (e->xcrossing.mode == NotifyGrab) { client->frame->max_press = client->frame->desk_press = client->frame->shade_press = client->frame->iconify_press = client->frame->close_press = FALSE; } break; case OB_FRAME_CONTEXT_FRAME: /* When the mouse leaves an animating window, don't use the corresponding enter events. Pretend like the animating window doesn't even exist..! */ if (frame_iconify_animating(client->frame)) event_end_ignore_all_enters(event_start_ignore_all_enters()); ob_debug_type(OB_DEBUG_FOCUS, "%sNotify mode %d detail %d on %lx", (e->type == EnterNotify ? "Enter" : "Leave"), e->xcrossing.mode, e->xcrossing.detail, (client?client->window:0)); if (grab_on_keyboard()) break; if (config_focus_follow && /* leave inferior events can happen when the mouse goes onto the window's border and then into the window before the delay is up */ e->xcrossing.detail != NotifyInferior) { if (config_focus_delay && focus_delay_timeout_id) g_source_remove(focus_delay_timeout_id); if (config_unfocus_leave) event_leave_client(client); } break; default: but = context_to_button(client->frame, con, FALSE); if (but) { *but = FALSE; if (e->xcrossing.mode == NotifyGrab) { but = context_to_button(client->frame, con, TRUE); *but = FALSE; } frame_adjust_state(client->frame); } break; } break; case EnterNotify: { con = frame_context(client, e->xcrossing.window, e->xcrossing.x, e->xcrossing.y); switch (con) { case OB_FRAME_CONTEXT_FRAME: if (grab_on_keyboard()) break; if (e->xcrossing.mode == NotifyGrab || (e->xcrossing.mode == NotifyUngrab && /* ungrab enters are used when _under_ mouse is being used */ !(config_focus_follow && config_focus_under_mouse)) || /*ignore enters when we're already in the window */ e->xcrossing.detail == NotifyInferior) { ob_debug_type(OB_DEBUG_FOCUS, "%sNotify mode %d detail %d serial %lu on %lx " "IGNORED", (e->type == EnterNotify ? "Enter" : "Leave"), e->xcrossing.mode, e->xcrossing.detail, e->xcrossing.serial, client?client->window:0); } else { ob_debug_type(OB_DEBUG_FOCUS, "%sNotify mode %d detail %d serial %lu on %lx, " "focusing window", (e->type == EnterNotify ? "Enter" : "Leave"), e->xcrossing.mode, e->xcrossing.detail, e->xcrossing.serial, (client?client->window:0)); if (config_focus_follow) { if (config_focus_delay && unfocus_delay_timeout_id) g_source_remove(unfocus_delay_timeout_id); event_enter_client(client); } } break; default: but = context_to_button(client->frame, con, FALSE); if (but) { *but = TRUE; if (e->xcrossing.mode == NotifyUngrab) { but = context_to_button(client->frame, con, TRUE); *but = (con == pcon); } frame_adjust_state(client->frame); } break; } break; } case ConfigureRequest: { /* dont compress these unless you're going to watch for property notifies in between (these can change what the configure would do to the window). also you can't compress stacking events */ gint x, y, w, h; gboolean move = FALSE; gboolean resize = FALSE; /* get the current area */ RECT_TO_DIMS(client->area, x, y, w, h); ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d " "visible %d", client->title, screen_desktop, client->wmstate, client->frame->visible); ob_debug(" x %d y %d w %d h %d b %d", x, y, w, h, client->border_width); if (e->xconfigurerequest.value_mask & CWBorderWidth) if (client->border_width != e->xconfigurerequest.border_width) { client->border_width = e->xconfigurerequest.border_width; /* if the border width is changing then that is the same as requesting a resize, but we don't actually change the client's border, so it will change their root coordinates (since they include the border width) and we need to a notify then */ move = TRUE; } if (e->xconfigurerequest.value_mask & CWStackMode) { ObClient *sibling = NULL; gulong ignore_start; gboolean ok = TRUE; /* get the sibling */ if (e->xconfigurerequest.value_mask & CWSibling) { ObWindow *win; win = window_find(e->xconfigurerequest.above); if (win && WINDOW_IS_CLIENT(win) && WINDOW_AS_CLIENT(win) != client) { sibling = WINDOW_AS_CLIENT(win); } else /* an invalid sibling was specified so don't restack at all, it won't make sense no matter what we do */ ok = FALSE; } if (ok) { if (!config_focus_under_mouse) ignore_start = event_start_ignore_all_enters(); stacking_restack_request(client, sibling, e->xconfigurerequest.detail); if (!config_focus_under_mouse) event_end_ignore_all_enters(ignore_start); } /* a stacking change moves the window without resizing */ move = TRUE; } if ((e->xconfigurerequest.value_mask & CWX) || (e->xconfigurerequest.value_mask & CWY) || (e->xconfigurerequest.value_mask & CWWidth) || (e->xconfigurerequest.value_mask & CWHeight)) { /* don't allow clients to move shaded windows (fvwm does this) */ if (e->xconfigurerequest.value_mask & CWX) { if (!client->shaded) x = e->xconfigurerequest.x; move = TRUE; } if (e->xconfigurerequest.value_mask & CWY) { if (!client->shaded) y = e->xconfigurerequest.y; move = TRUE; } if (e->xconfigurerequest.value_mask & CWWidth) { w = e->xconfigurerequest.width; resize = TRUE; } if (e->xconfigurerequest.value_mask & CWHeight) { h = e->xconfigurerequest.height; resize = TRUE; } } ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d " "move %d resize %d", e->xconfigurerequest.value_mask & CWX, x, e->xconfigurerequest.value_mask & CWY, y, e->xconfigurerequest.value_mask & CWWidth, w, e->xconfigurerequest.value_mask & CWHeight, h, move, resize); /* check for broken apps moving to their root position XXX remove this some day...that would be nice. right now all kde apps do this when they try activate themselves on another desktop. eg. open amarok window on desktop 1, switch to desktop 2, click amarok tray icon. it will move by its decoration size. */ if (x != client->area.x && x == (client->frame->area.x + client->frame->size.left - (gint)client->border_width) && y != client->area.y && y == (client->frame->area.y + client->frame->size.top - (gint)client->border_width) && w == client->area.width && h == client->area.height) { ob_debug_type(OB_DEBUG_APP_BUGS, "Application %s is trying to move via " "ConfigureRequest to it's root window position " "but it is not using StaticGravity", client->title); /* don't move it */ x = client->area.x; y = client->area.y; /* they still requested a move, so don't change whether a notify is sent or not */ } /* check for broken apps (java swing) moving to 0,0 when there is a strut there. XXX remove this some day...that would be nice. but really unexpected from Sun Microsystems. */ if (x == 0 && y == 0 && client->gravity == NorthWestGravity && client_normal(client)) { const Rect to = { x, y, w, h }; /* oldschool fullscreen windows are allowed */ if (!client_is_oldfullscreen(client, &to)) { Rect *r; r = screen_area(client->desktop, SCREEN_AREA_ALL_MONITORS, NULL); if (r->x || r->y) { /* move the window only to the corner outside struts */ x = r->x; y = r->y; ob_debug_type(OB_DEBUG_APP_BUGS, "Application %s is trying to move via " "ConfigureRequest to 0,0 using " "NorthWestGravity, while there is a " "strut there. " "Moving buggy app from (0,0) to (%d,%d)", client->title, r->x, r->y); } g_slice_free(Rect, r); /* they still requested a move, so don't change whether a notify is sent or not */ } } { gint lw, lh; client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE); /* if x was not given, then use gravity to figure out the new x. the reference point should not be moved */ if ((e->xconfigurerequest.value_mask & CWWidth && !(e->xconfigurerequest.value_mask & CWX))) client_gravity_resize_w(client, &x, client->area.width, w); /* same for y */ if ((e->xconfigurerequest.value_mask & CWHeight && !(e->xconfigurerequest.value_mask & CWY))) client_gravity_resize_h(client, &y, client->area.height,h); client_find_onscreen(client, &x, &y, w, h, FALSE); ob_debug("Granting ConfigureRequest x %d y %d w %d h %d", x, y, w, h); client_configure(client, x, y, w, h, FALSE, TRUE, TRUE); } break; } case UnmapNotify: ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d " "ignores left %d", client->window, e->xunmap.event, e->xunmap.from_configure, client->ignore_unmaps); if (client->ignore_unmaps) { client->ignore_unmaps--; break; } client_unmanage(client); break; case DestroyNotify: ob_debug("DestroyNotify for window 0x%x", client->window); client_unmanage(client); break; case ReparentNotify: /* this is when the client is first taken captive in the frame */ if (e->xreparent.parent == client->frame->window) break; /* This event is quite rare and is usually handled in unmapHandler. However, if the window is unmapped when the reparent event occurs, the window manager never sees it because an unmap event is not sent to an already unmapped window. */ ob_debug("ReparentNotify for window 0x%x", client->window); client_unmanage(client); break; case MapRequest: ob_debug("MapRequest for 0x%lx", client->window); if (!client->iconic) break; /* this normally doesn't happen, but if it does, we don't want it! it can happen now when the window is on another desktop, but we still don't want it! */ client_activate(client, FALSE, FALSE, TRUE, TRUE, TRUE); break; case ClientMessage: /* validate cuz we query stuff off the client here */ if (!client_validate(client)) break; if (e->xclient.format != 32) return; msgtype = e->xclient.message_type; if (msgtype == OBT_PROP_ATOM(WM_CHANGE_STATE)) { if (!more_client_message_event(client->window, msgtype)) client_set_wm_state(client, e->xclient.data.l[0]); } else if (msgtype == OBT_PROP_ATOM(NET_WM_DESKTOP)) { if (!more_client_message_event(client->window, msgtype) && ((unsigned)e->xclient.data.l[0] < screen_num_desktops || (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)) { client_set_desktop(client, (unsigned)e->xclient.data.l[0], FALSE, FALSE); } } else if (msgtype == OBT_PROP_ATOM(NET_WM_STATE)) { gulong ignore_start; /* can't compress these */ ob_debug("net_wm_state %s %ld %ld for 0x%lx", (e->xclient.data.l[0] == 0 ? "Remove" : e->xclient.data.l[0] == 1 ? "Add" : e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"), e->xclient.data.l[1], e->xclient.data.l[2], client->window); /* ignore enter events caused by these like ob actions do */ if (!config_focus_under_mouse) ignore_start = event_start_ignore_all_enters(); client_set_state(client, e->xclient.data.l[0], e->xclient.data.l[1], e->xclient.data.l[2]); if (!config_focus_under_mouse) event_end_ignore_all_enters(ignore_start); } else if (msgtype == OBT_PROP_ATOM(NET_CLOSE_WINDOW)) { ob_debug("net_close_window for 0x%lx", client->window); client_close(client); } else if (msgtype == OBT_PROP_ATOM(NET_ACTIVE_WINDOW)) { ob_debug("net_active_window for 0x%lx source=%s", client->window, (e->xclient.data.l[0] == 0 ? "unknown" : (e->xclient.data.l[0] == 1 ? "application" : (e->xclient.data.l[0] == 2 ? "user" : "INVALID")))); /* XXX make use of data.l[2] !? */ if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) { /* we can not trust the timestamp from applications. e.g. chromium passes a very old timestamp. openbox thinks the window will get focus and calls XSetInputFocus with the (old) timestamp, which doesn't end up moving focus at all. but the window is raised, not hilited, etc, as if it was really going to get focus. so do not use this timestamp in event_curtime, as this would be used in XSetInputFocus. */ event_sourcetime = e->xclient.data.l[1]; if (e->xclient.data.l[1] == 0) ob_debug_type(OB_DEBUG_APP_BUGS, "_NET_ACTIVE_WINDOW message for window %s is" " missing a timestamp", client->title); } else ob_debug_type(OB_DEBUG_APP_BUGS, "_NET_ACTIVE_WINDOW message for window %s is " "missing source indication", client->title); /* TODO(danakj) This should use (e->xclient.data.l[0] == 0 || e->xclient.data.l[0] == 2) to determine if a user requested the activation, however GTK+ applications seem unable to make this distinction ever (including panels such as xfce4-panel and gnome-panel). So we are left just assuming all activations are from the user. */ client_activate(client, FALSE, FALSE, TRUE, TRUE, TRUE); } else if (msgtype == OBT_PROP_ATOM(NET_WM_MOVERESIZE)) { ob_debug("net_wm_moveresize for 0x%lx direction %d", client->window, e->xclient.data.l[2]); if ((Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOP) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPRIGHT) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOM) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_LEFT) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_KEYBOARD) || (Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE_KEYBOARD)) { moveresize_start(client, e->xclient.data.l[0], e->xclient.data.l[1], e->xclient.data.l[3], e->xclient.data.l[2]); } else if ((Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_CANCEL)) if (moveresize_client) moveresize_end(TRUE); } else if (msgtype == OBT_PROP_ATOM(NET_MOVERESIZE_WINDOW)) { gint ograv, x, y, w, h; ograv = client->gravity; if (e->xclient.data.l[0] & 0xff) client->gravity = e->xclient.data.l[0] & 0xff; if (e->xclient.data.l[0] & 1 << 8) x = e->xclient.data.l[1]; else x = client->area.x; if (e->xclient.data.l[0] & 1 << 9) y = e->xclient.data.l[2]; else y = client->area.y; if (e->xclient.data.l[0] & 1 << 10) { w = e->xclient.data.l[3]; /* if x was not given, then use gravity to figure out the new x. the reference point should not be moved */ if (!(e->xclient.data.l[0] & 1 << 8)) client_gravity_resize_w(client, &x, client->area.width, w); } else w = client->area.width; if (e->xclient.data.l[0] & 1 << 11) { h = e->xclient.data.l[4]; /* same for y */ if (!(e->xclient.data.l[0] & 1 << 9)) client_gravity_resize_h(client, &y, client->area.height,h); } else h = client->area.height; ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)", e->xclient.data.l[0] & 1 << 8, x, e->xclient.data.l[0] & 1 << 9, y, client->gravity); client_find_onscreen(client, &x, &y, w, h, FALSE); client_configure(client, x, y, w, h, FALSE, TRUE, FALSE); client->gravity = ograv; } else if (msgtype == OBT_PROP_ATOM(NET_RESTACK_WINDOW)) { if (e->xclient.data.l[0] != 2) { ob_debug_type(OB_DEBUG_APP_BUGS, "_NET_RESTACK_WINDOW sent for window %s with " "invalid source indication %ld", client->title, e->xclient.data.l[0]); } else { ObClient *sibling = NULL; if (e->xclient.data.l[1]) { ObWindow *win = window_find(e->xclient.data.l[1]); if (WINDOW_IS_CLIENT(win) && WINDOW_AS_CLIENT(win) != client) { sibling = WINDOW_AS_CLIENT(win); } if (sibling == NULL) ob_debug_type(OB_DEBUG_APP_BUGS, "_NET_RESTACK_WINDOW sent for window %s " "with invalid sibling 0x%x", client->title, e->xclient.data.l[1]); } if (e->xclient.data.l[2] == Below || e->xclient.data.l[2] == BottomIf || e->xclient.data.l[2] == Above || e->xclient.data.l[2] == TopIf || e->xclient.data.l[2] == Opposite) { gulong ignore_start; if (!config_focus_under_mouse) ignore_start = event_start_ignore_all_enters(); /* just raise, don't activate */ stacking_restack_request(client, sibling, e->xclient.data.l[2]); if (!config_focus_under_mouse) event_end_ignore_all_enters(ignore_start); /* send a synthetic ConfigureNotify, cuz this is supposed to be like a ConfigureRequest. */ client_reconfigure(client, TRUE); } else ob_debug_type(OB_DEBUG_APP_BUGS, "_NET_RESTACK_WINDOW sent for window %s " "with invalid detail %d", client->title, e->xclient.data.l[2]); } } break; case PropertyNotify: /* validate cuz we query stuff off the client here */ if (!client_validate(client)) break; msgtype = e->xproperty.atom; /* ignore changes to some properties if there is another change coming in the queue */ { struct ObSkipPropertyChange s; s.window = client->window; s.prop = msgtype; if (xqueue_exists_local(skip_property_change, &s)) break; } msgtype = e->xproperty.atom; if (msgtype == XA_WM_NORMAL_HINTS) { int x, y, w, h, lw, lh; ob_debug("Update NORMAL hints"); client_update_normal_hints(client); /* normal hints can make a window non-resizable */ client_setup_decor_and_functions(client, FALSE); x = client->area.x; y = client->area.y; w = client->area.width; h = client->area.height; /* apply the new normal hints */ client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE); /* make sure the window is visible, and if the window is resized off-screen due to the normal hints changing then this will push it back onto the screen. */ client_find_onscreen(client, &x, &y, w, h, FALSE); /* make sure the client's sizes are within its bounds, but don't make it reply with a configurenotify unless something changed. emacs will update its normal hints every time it receives a configurenotify */ client_configure(client, x, y, w, h, FALSE, TRUE, FALSE); } else if (msgtype == OBT_PROP_ATOM(MOTIF_WM_HINTS)) { client_get_mwm_hints(client); /* This can override some mwm hints */ client_get_type_and_transientness(client); /* Apply the changes to the window */ client_setup_decor_and_functions(client, TRUE); } else if (msgtype == XA_WM_HINTS) { client_update_wmhints(client); } else if (msgtype == XA_WM_TRANSIENT_FOR) { /* get the transient-ness first, as this affects if the client decides to be transient for the group or not in client_update_transient_for() */ client_get_type_and_transientness(client); client_update_transient_for(client); /* type may have changed, so update the layer */ client_calc_layer(client); client_setup_decor_and_functions(client, TRUE); } else if (msgtype == OBT_PROP_ATOM(NET_WM_NAME) || msgtype == OBT_PROP_ATOM(WM_NAME) || msgtype == OBT_PROP_ATOM(NET_WM_ICON_NAME) || msgtype == OBT_PROP_ATOM(WM_ICON_NAME)) { client_update_title(client); } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) { client_update_protocols(client); } else if (msgtype == OBT_PROP_ATOM(NET_WM_STRUT) || msgtype == OBT_PROP_ATOM(NET_WM_STRUT_PARTIAL)) { client_update_strut(client); } else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON)) { client_update_icons(client); } else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON_GEOMETRY)) { client_update_icon_geometry(client); } else if (msgtype == OBT_PROP_ATOM(NET_WM_USER_TIME)) { guint32 t; if (client == focus_client && OBT_PROP_GET32(client->window, NET_WM_USER_TIME, CARDINAL, &t) && t && !event_time_after(t, e->xproperty.time) && (!event_last_user_time || event_time_after(t, event_last_user_time))) { event_last_user_time = t; } } else if (msgtype == OBT_PROP_ATOM(NET_WM_WINDOW_OPACITY)) { client_update_opacity(client); } #ifdef SYNC else if (msgtype == OBT_PROP_ATOM(NET_WM_SYNC_REQUEST_COUNTER)) { /* if they are resizing right now this would cause weird behaviour. if one day a user reports clients stop resizing, then handle this better by resetting a new XSync alarm and stuff on the new counter, but I expect it will never happen */ if (moveresize_client == client) moveresize_end(FALSE); client_update_sync_request_counter(client); } #endif break; case ColormapNotify: client_update_colormap(client, e->xcolormap.colormap); break; default: ; #ifdef SHAPE { int kind; if (obt_display_extension_shape && e->type == obt_display_extension_shape_basep) { switch (((XShapeEvent*)e)->kind) { case ShapeBounding: case ShapeClip: client->shaped = ((XShapeEvent*)e)->shaped; kind = ShapeBounding; break; #ifdef ShapeInput case ShapeInput: client->shaped_input = ((XShapeEvent*)e)->shaped; kind = ShapeInput; break; #endif default: g_assert_not_reached(); } frame_adjust_shape_kind(client->frame, kind); } } #endif } } static void event_handle_dock(ObDock *s, XEvent *e) { switch (e->type) { case EnterNotify: dock_hide(FALSE); break; case LeaveNotify: /* don't hide when moving into a dock app */ if (e->xcrossing.detail != NotifyInferior) dock_hide(TRUE); break; } } static void event_handle_dockapp(ObDockApp *app, XEvent *e) { switch (e->type) { case MotionNotify: dock_app_drag(app, &e->xmotion); break; case UnmapNotify: if (app->ignore_unmaps) { app->ignore_unmaps--; break; } dock_unmanage(app, TRUE); break; case DestroyNotify: case ReparentNotify: dock_unmanage(app, FALSE); break; case ConfigureNotify: dock_app_configure(app, e->xconfigure.width, e->xconfigure.height); break; } } static ObMenuFrame* find_active_menu(void) { GList *it; ObMenuFrame *ret = NULL; for (it = menu_frame_visible; it; it = g_list_next(it)) { ret = it->data; if (ret->selected) break; ret = NULL; } return ret; } static ObMenuFrame* find_active_or_last_menu(void) { ObMenuFrame *ret = NULL; ret = find_active_menu(); if (!ret && menu_frame_visible) ret = menu_frame_visible->data; return ret; } static gboolean event_handle_prompt(ObPrompt *p, XEvent *e) { switch (e->type) { case ButtonPress: case ButtonRelease: case MotionNotify: return prompt_mouse_event(p, e); break; case KeyPress: return prompt_key_event(p, e); break; } return FALSE; } static gboolean event_handle_menu_input(XEvent *ev) { gboolean ret = FALSE; if (ev->type == ButtonRelease || ev->type == ButtonPress) { ObMenuEntryFrame *e; if ((ev->xbutton.button < 4 || ev->xbutton.button > 5) && ((ev->type == ButtonRelease && menu_hide_delay_reached()) || ev->type == ButtonPress)) { if ((e = menu_entry_frame_under(ev->xbutton.x_root, ev->xbutton.y_root))) { if (ev->type == ButtonPress && e->frame->child) menu_frame_select(e->frame->child, NULL, TRUE); menu_frame_select(e->frame, e, TRUE); if (ev->type == ButtonRelease) menu_entry_frame_execute(e, ev->xbutton.state); } else menu_frame_hide_all(); } ret = TRUE; } else if (ev->type == KeyPress || ev->type == KeyRelease) { guint mods; ObMenuFrame *frame; /* get the modifiers */ mods = obt_keyboard_only_modmasks(ev->xkey.state); frame = find_active_or_last_menu(); if (frame == NULL) g_assert_not_reached(); /* there is no active menu */ /* Allow control while going thru the menu */ else if (ev->type == KeyPress && (mods & ~ControlMask) == 0) { gunichar unikey; KeySym sym; frame->got_press = TRUE; frame->press_keycode = ev->xkey.keycode; frame->press_doexec = FALSE; sym = obt_keyboard_keypress_to_keysym(ev); if (sym == XK_Escape) { menu_frame_hide_all(); ret = TRUE; } else if (sym == XK_Left) { /* Left goes to the parent menu */ if (frame->parent) { /* remove focus from the child */ menu_frame_select(frame, NULL, TRUE); /* and put it in the parent */ menu_frame_select(frame->parent, frame->parent->selected, TRUE); } ret = TRUE; } else if (sym == XK_Right || sym == XK_Return || sym == XK_KP_Enter) { /* Right and enter goes to the selected submenu. Enter executes instead if it's not on a submenu. */ if (frame->selected) { const ObMenuEntryType t = frame->selected->entry->type; if (t == OB_MENU_ENTRY_TYPE_SUBMENU) { /* make sure it is visible */ menu_frame_select(frame, frame->selected, TRUE); /* move focus to the child menu */ menu_frame_select_next(frame->child); } else if (sym != XK_Right) { frame->press_doexec = TRUE; } } ret = TRUE; } else if (sym == XK_Up) { menu_frame_select_previous(frame); ret = TRUE; } else if (sym == XK_Down) { menu_frame_select_next(frame); ret = TRUE; } else if (sym == XK_Home) { menu_frame_select_first(frame); ret = TRUE; } else if (sym == XK_End) { menu_frame_select_last(frame); ret = TRUE; } /* keyboard accelerator shortcuts. (if it was a valid key) */ else if (frame->entries && (unikey = obt_keyboard_keypress_to_unichar(menu_frame_ic(frame), ev))) { GList *start; GList *it; ObMenuEntryFrame *found = NULL; guint num_found = 0; /* start after the selected one */ start = frame->entries; if (frame->selected) { for (it = start; frame->selected != it->data; it = g_list_next(it)) g_assert(it != NULL); /* nothing was selected? */ /* next with wraparound */ start = g_list_next(it); if (start == NULL) start = frame->entries; } it = start; do { ObMenuEntryFrame *e = it->data; gunichar entrykey = 0; if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL) entrykey = e->entry->data.normal.shortcut; else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) entrykey = e->entry->data.submenu.submenu->shortcut; if (unikey == entrykey) { if (found == NULL) found = e; ++num_found; } /* next with wraparound */ it = g_list_next(it); if (it == NULL) it = frame->entries; } while (it != start); if (found) { menu_frame_select(frame, found, TRUE); if (num_found == 1) { if (found->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) { /* move focus to the child menu */ menu_frame_select_next(frame->child); } else { frame->press_doexec = TRUE; } } ret = TRUE; } } } /* Use KeyRelease events for running things so that the key release doesn't get sent to the focused application. Allow ControlMask only, and don't bother if the menu is empty */ else if (ev->type == KeyRelease && (mods & ~ControlMask) == 0) { if (frame->press_keycode == ev->xkey.keycode && frame->got_press && frame->press_doexec) { if (frame->selected) menu_entry_frame_execute(frame->selected, ev->xkey.state); } } } return ret; } static gboolean event_look_for_menu_enter(XEvent *ev, gpointer data) { const ObMenuFrame *f = (ObMenuFrame*)data; ObMenuEntryFrame *e; return ev->type == EnterNotify && (e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) && e->frame == f && !e->ignore_enters; } static void event_handle_menu(ObMenuFrame *frame, XEvent *ev) { ObMenuFrame *f; ObMenuEntryFrame *e; switch (ev->type) { case MotionNotify: /* We need to catch MotionNotify in addition to EnterNotify because it is possible for the menu to be opened under the mouse cursor, and moving the mouse should select the item. */ if ((e = g_hash_table_lookup(menu_frame_map, &ev->xmotion.window))) { if (e->ignore_enters) --e->ignore_enters; else if (!(f = find_active_menu()) || f == e->frame || f->parent == e->frame || f->child == e->frame) menu_frame_select(e->frame, e, FALSE); } break; case EnterNotify: if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) { if (e->ignore_enters) --e->ignore_enters; else if (!(f = find_active_menu()) || f == e->frame || f->parent == e->frame || f->child == e->frame) menu_frame_select(e->frame, e, FALSE); } break; case LeaveNotify: /* ignore leaves when we're already in the window */ if (ev->xcrossing.detail == NotifyInferior) break; if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) { /* check if an EnterNotify event is coming, and if not, then select nothing in the menu */ if (!xqueue_exists_local(event_look_for_menu_enter, e->frame)) menu_frame_select(e->frame, NULL, FALSE); } break; } } static gboolean event_handle_user_input(ObClient *client, XEvent *e) { g_assert(e->type == ButtonPress || e->type == ButtonRelease || e->type == MotionNotify || e->type == KeyPress || e->type == KeyRelease); if (menu_frame_visible) { if (event_handle_menu_input(e)) /* don't use the event if the menu used it, but if the menu didn't use it and it's a keypress that is bound, it will close the menu and be used */ return TRUE; } /* if the keyboard interactive action uses the event then dont use it for bindings. likewise is moveresize uses the event. */ if (actions_interactive_input_event(e) || moveresize_event(e)) return TRUE; if (moveresize_in_progress) /* make further actions work on the client being moved/resized */ client = moveresize_client; if (e->type == ButtonPress || e->type == ButtonRelease || e->type == MotionNotify) { /* the frame may not be "visible" but they can still click on it in the case where it is animating before disappearing */ if (!client || !frame_iconify_animating(client->frame)) return mouse_event(client, e); } else return keyboard_event((focus_cycle_target ? focus_cycle_target : (client ? client : focus_client)), e); return FALSE; } static void focus_delay_dest(gpointer data) { g_slice_free(ObFocusDelayData, data); focus_delay_timeout_id = 0; focus_delay_timeout_client = NULL; } static void unfocus_delay_dest(gpointer data) { g_slice_free(ObFocusDelayData, data); unfocus_delay_timeout_id = 0; unfocus_delay_timeout_client = NULL; } static gboolean focus_delay_func(gpointer data) { ObFocusDelayData *d = data; Time old = event_curtime; /* save the curtime */ event_curtime = d->time; event_curserial = d->serial; if (client_focus(d->client) && config_focus_raise) stacking_raise(CLIENT_AS_WINDOW(d->client)); event_curtime = old; return FALSE; /* no repeat */ } static gboolean unfocus_delay_func(gpointer data) { ObFocusDelayData *d = data; Time old = event_curtime; /* save the curtime */ event_curtime = d->time; event_curserial = d->serial; focus_nothing(); event_curtime = old; return FALSE; /* no repeat */ } static void focus_delay_client_dest(ObClient *client, gpointer data) { if (focus_delay_timeout_client == client && focus_delay_timeout_id) g_source_remove(focus_delay_timeout_id); if (unfocus_delay_timeout_client == client && unfocus_delay_timeout_id) g_source_remove(unfocus_delay_timeout_id); } void event_halt_focus_delay(void) { /* ignore all enter events up till the event which caused this to occur */ if (event_curserial) event_ignore_enter_range(1, event_curserial); if (focus_delay_timeout_id) g_source_remove(focus_delay_timeout_id); if (unfocus_delay_timeout_id) g_source_remove(unfocus_delay_timeout_id); } gulong event_start_ignore_all_enters(void) { return NextRequest(obt_display); } static void event_ignore_enter_range(gulong start, gulong end) { ObSerialRange *r; g_assert(start != 0); g_assert(end != 0); r = g_slice_new(ObSerialRange); r->start = start; r->end = end; ignore_serials = g_slist_prepend(ignore_serials, r); ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu", r->start, r->end); /* increment the serial so we don't ignore events we weren't meant to */ OBT_PROP_ERASE(screen_support_win, MOTIF_WM_HINTS); } void event_end_ignore_all_enters(gulong start) { /* Use (NextRequest-1) so that we ignore up to the current serial only. Inside event_ignore_enter_range, we increment the serial by one, but if we ignore that serial too, then any enter events generated by mouse movement will be ignored until we create some further network traffic. Instead ignore up to NextRequest-1, then when we increment the serial, we will be *past* the range of ignored serials */ event_ignore_enter_range(start, NextRequest(obt_display)-1); } static gboolean is_enter_focus_event_ignored(gulong serial) { GSList *it, *next; for (it = ignore_serials; it; it = next) { ObSerialRange *r = it->data; next = g_slist_next(it); if ((glong)(serial - r->end) > 0) { /* past the end */ ignore_serials = g_slist_delete_link(ignore_serials, it); g_slice_free(ObSerialRange, r); } else if ((glong)(serial - r->start) >= 0) return TRUE; } return FALSE; } void event_cancel_all_key_grabs(void) { if (actions_interactive_act_running()) { actions_interactive_cancel_act(); ob_debug("KILLED interactive action"); } else if (menu_frame_visible) { menu_frame_hide_all(); ob_debug("KILLED open menus"); } else if (moveresize_in_progress) { moveresize_end(TRUE); ob_debug("KILLED interactive moveresize"); } else if (grab_on_keyboard()) { ungrab_keyboard(); ob_debug("KILLED active grab on keyboard"); } else ungrab_passive_key(); XSync(obt_display, FALSE); } gboolean event_time_after(guint32 t1, guint32 t2) { g_assert(t1 != CurrentTime); g_assert(t2 != CurrentTime); /* Timestamp values wrap around (after about 49.7 days). The server, given its current time is represented by timestamp T, always interprets timestamps from clients by treating half of the timestamp space as being later in time than T. - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html */ /* TIME_HALF is not half of the number space of a Time type variable. * Rather, it is half the number space of a timestamp value, which is * always 32 bits. */ #define TIME_HALF (guint32)(1 << 31) if (t2 >= TIME_HALF) /* t2 is in the second half so t1 might wrap around and be smaller than t2 */ return t1 >= t2 || t1 < (t2 + TIME_HALF); else /* t2 is in the first half so t1 has to come after it */ return t1 >= t2 && t1 < (t2 + TIME_HALF); } gboolean find_timestamp(XEvent *e, gpointer data) { const Time t = event_get_timestamp(e); if (t && t >= event_curtime) { event_curtime = t; return TRUE; } else return FALSE; } static Time next_time(void) { /* Some events don't come with timestamps :( ...but we can get one anyways >:) */ /* Generate a timestamp so there is guaranteed at least one in the queue eventually */ XChangeProperty(obt_display, screen_support_win, OBT_PROP_ATOM(WM_CLASS), OBT_PROP_ATOM(STRING), 8, PropModeAppend, NULL, 0); /* Grab the first timestamp available */ xqueue_exists(find_timestamp, NULL); /*g_assert(event_curtime != CurrentTime);*/ /* Save the time so we don't have to do this again for this event */ return event_curtime; } Time event_time(void) { if (event_curtime) return event_curtime; return next_time(); } Time event_source_time(void) { return event_sourcetime; } void event_reset_time(void) { next_time(); } void event_update_user_time(void) { event_last_user_time = event_time(); } void event_reset_user_time(void) { event_last_user_time = CurrentTime; }