1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 event.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003 Ben Jansens
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 See the COPYING file for a copy of the GNU General Public License.
32 #include "menuframe.h"
36 #include "framerender.h"
38 #include "moveresize.h"
41 #include "extensions.h"
44 #include <X11/keysym.h>
45 #include <X11/Xatom.h>
48 #ifdef HAVE_SYS_SELECT_H
49 # include <sys/select.h>
55 # include <X11/XKBlib.h>
59 #include <X11/ICE/ICElib.h>
73 static void event_process(const XEvent *e, gpointer data);
74 static void event_handle_root(XEvent *e);
75 static void event_handle_menu(XEvent *e);
76 static void event_handle_dock(ObDock *s, XEvent *e);
77 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
78 static void event_handle_client(ObClient *c, XEvent *e);
79 static void event_handle_group(ObGroup *g, XEvent *e);
81 static void focus_delay_dest(gpointer data);
82 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
83 static gboolean focus_delay_func(gpointer data);
84 static void focus_delay_client_dest(ObClient *client, gpointer data);
86 static gboolean menu_hide_delay_func(gpointer data);
88 /* The time for the current event being processed */
89 Time event_curtime = CurrentTime;
91 /*! The value of the mask for the NumLock modifier */
93 /*! The value of the mask for the ScrollLock modifier */
95 /*! The key codes for the modifier keys */
96 static XModifierKeymap *modmap;
97 /*! Table of the constant modifier masks */
98 static const gint mask_table[] = {
99 ShiftMask, LockMask, ControlMask, Mod1Mask,
100 Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
102 static gint mask_table_size;
104 static guint ignore_enter_focus = 0;
106 static gboolean menu_can_hide;
109 static void ice_handler(gint fd, gpointer conn)
112 IceProcessMessages(conn, NULL, &b);
115 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
116 IcePointer *watch_data)
121 fd = IceConnectionNumber(conn);
122 ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
124 ob_main_loop_fd_remove(ob_main_loop, fd);
130 void event_startup(gboolean reconfig)
132 if (reconfig) return;
134 mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
136 /* get lock masks that are defined by the display (not constant) */
137 modmap = XGetModifierMapping(ob_display);
139 if (modmap && modmap->max_keypermod > 0) {
141 const size_t size = mask_table_size * modmap->max_keypermod;
142 /* get the values of the keyboard lock modifiers
143 Note: Caps lock is not retrieved the same way as Scroll and Num
144 lock since it doesn't need to be. */
145 const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
146 const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
149 for (cnt = 0; cnt < size; ++cnt) {
150 if (! modmap->modifiermap[cnt]) continue;
152 if (num_lock == modmap->modifiermap[cnt])
153 NumLockMask = mask_table[cnt / modmap->max_keypermod];
154 if (scroll_lock == modmap->modifiermap[cnt])
155 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
159 ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
162 IceAddConnectionWatch(ice_watch, NULL);
165 client_add_destructor(focus_delay_client_dest, NULL);
168 void event_shutdown(gboolean reconfig)
170 if (reconfig) return;
173 IceRemoveConnectionWatch(ice_watch, NULL);
176 client_remove_destructor(focus_delay_client_dest);
177 XFreeModifiermap(modmap);
180 static Window event_get_window(XEvent *e)
187 window = RootWindow(ob_display, ob_screen);
190 window = e->xmap.window;
193 window = e->xunmap.window;
196 window = e->xdestroywindow.window;
198 case ConfigureRequest:
199 window = e->xconfigurerequest.window;
201 case ConfigureNotify:
202 window = e->xconfigure.window;
206 if (extensions_xkb && e->type == extensions_xkb_event_basep) {
207 switch (((XkbAnyEvent*)e)->xkb_type) {
209 window = ((XkbBellNotifyEvent*)e)->window;
215 window = e->xany.window;
220 static void event_set_curtime(XEvent *e)
222 Time t = CurrentTime;
224 /* grab the lasttime and hack up the state */
240 t = e->xproperty.time;
244 t = e->xcrossing.time;
247 /* if more event types are anticipated, get their timestamp
255 #define STRIP_MODS(s) \
256 s &= ~(LockMask | NumLockMask | ScrollLockMask), \
257 /* kill off the Button1Mask etc, only want the modifiers */ \
258 s &= (ControlMask | ShiftMask | Mod1Mask | \
259 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
261 static void event_hack_mods(XEvent *e)
264 XkbStateRec xkb_state;
272 STRIP_MODS(e->xbutton.state);
275 STRIP_MODS(e->xkey.state);
278 STRIP_MODS(e->xkey.state);
279 /* remove from the state the mask of the modifier being released, if
280 it is a modifier key being released (this is a little ugly..) */
282 if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
283 e->xkey.state = xkb_state.compat_state;
287 kp = modmap->modifiermap;
288 for (i = 0; i < mask_table_size; ++i) {
289 for (k = 0; k < modmap->max_keypermod; ++k) {
290 if (*kp == e->xkey.keycode) { /* found the keycode */
291 /* remove the mask for it */
292 e->xkey.state &= ~mask_table[i];
293 /* cause the first loop to break; */
295 break; /* get outta here! */
302 STRIP_MODS(e->xmotion.state);
303 /* compress events */
306 while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
308 e->xmotion.x_root = ce.xmotion.x_root;
309 e->xmotion.y_root = ce.xmotion.y_root;
316 static gboolean wanted_focusevent(XEvent *e)
318 gint mode = e->xfocus.mode;
319 gint detail = e->xfocus.detail;
320 Window win = e->xany.window;
322 if (e->type == FocusIn) {
324 /* These are ones we never want.. */
326 /* This means focus was given by a keyboard/mouse grab. */
327 if (mode == NotifyGrab)
329 /* This means focus was given back from a keyboard/mouse grab. */
330 if (mode == NotifyUngrab)
333 /* These are the ones we want.. */
335 if (win == RootWindow(ob_display, ob_screen)) {
336 /* This means focus reverted off of a client */
337 if (detail == NotifyPointerRoot || detail == NotifyDetailNone ||
338 detail == NotifyInferior)
344 /* This means focus moved from the root window to a client */
345 if (detail == NotifyVirtual)
347 /* This means focus moved from one client to another */
348 if (detail == NotifyNonlinearVirtual)
354 g_assert(e->type == FocusOut);
357 /* These are ones we never want.. */
359 /* This means focus was taken by a keyboard/mouse grab. */
360 if (mode == NotifyGrab)
363 /* Focus left the root window revertedto state */
364 if (win == RootWindow(ob_display, ob_screen))
367 /* These are the ones we want.. */
369 /* This means focus moved from a client to the root window */
370 if (detail == NotifyVirtual)
372 /* This means focus moved from one client to another */
373 if (detail == NotifyNonlinearVirtual)
381 static Bool look_for_focusin(Display *d, XEvent *e, XPointer arg)
383 return e->type == FocusIn && wanted_focusevent(e);
386 static gboolean event_ignore(XEvent *e, ObClient *client)
390 if (!wanted_focusevent(e))
396 if (!wanted_focusevent(e))
403 static void event_process(const XEvent *ec, gpointer data)
406 ObGroup *group = NULL;
407 ObClient *client = NULL;
409 ObDockApp *dockapp = NULL;
410 ObWindow *obwin = NULL;
412 ObEventData *ed = data;
414 /* make a copy we can mangle */
418 window = event_get_window(e);
419 if (!(e->type == PropertyNotify &&
420 (group = g_hash_table_lookup(group_map, &window))))
421 if ((obwin = g_hash_table_lookup(window_map, &window))) {
422 switch (obwin->type) {
424 dock = WINDOW_AS_DOCK(obwin);
427 dockapp = WINDOW_AS_DOCKAPP(obwin);
430 client = WINDOW_AS_CLIENT(obwin);
433 case Window_Internal:
434 /* not to be used for events */
435 g_assert_not_reached();
440 event_set_curtime(e);
442 if (event_ignore(e, client)) {
449 /* deal with it in the kernel */
451 if (menu_frame_visible &&
452 (e->type == EnterNotify || e->type == LeaveNotify))
454 /* crossing events for menu */
455 event_handle_menu(e);
456 } else if (e->type == FocusIn) {
457 if (client && client != focus_client) {
458 frame_adjust_focus(client->frame, TRUE);
459 focus_set_client(client);
460 client_calc_layer(client);
462 } else if (e->type == FocusOut) {
463 gboolean nomove = FALSE;
466 ob_debug_type(OB_DEBUG_FOCUS, "FocusOut Event\n");
468 /* Look for the followup FocusIn */
469 if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
470 /* There is no FocusIn, this means focus went to a window that
471 is not being managed, or a window on another screen. */
472 ob_debug_type(OB_DEBUG_FOCUS, "Focus went to a black hole !\n");
473 /* nothing is focused */
474 focus_set_client(NULL);
475 } else if (ce.xany.window == e->xany.window) {
476 /* If focus didn't actually move anywhere, there is nothing to do*/
478 } else if (ce.xfocus.detail == NotifyPointerRoot ||
479 ce.xfocus.detail == NotifyDetailNone ||
480 ce.xfocus.detail == NotifyInferior) {
481 ob_debug_type(OB_DEBUG_FOCUS, "Focus went to root\n");
482 /* Focus has been reverted to the root window or nothing
483 FocusOut events come after UnmapNotify, so we don't need to
484 worry about focusing an invalid window
486 focus_fallback(TRUE);
488 /* Focus did move, so process the FocusIn event */
489 ObEventData ed = { .ignored = FALSE };
490 event_process(&ce, &ed);
492 /* The FocusIn was ignored, this means it was on a window
493 that isn't a client. */
494 ob_debug_type(OB_DEBUG_FOCUS,
495 "Focus went to an unmanaged window 0x%x !\n",
497 focus_fallback(TRUE);
501 if (client && !nomove) {
502 frame_adjust_focus(client->frame, FALSE);
503 /* focus_set_client has already been called for sure */
504 client_calc_layer(client);
507 event_handle_group(group, e);
509 event_handle_client(client, e);
511 event_handle_dockapp(dockapp, e);
513 event_handle_dock(dock, e);
514 else if (window == RootWindow(ob_display, ob_screen))
515 event_handle_root(e);
516 else if (e->type == MapRequest)
517 client_manage(window);
518 else if (e->type == ConfigureRequest) {
519 /* unhandled configure requests must be used to configure the
523 xwc.x = e->xconfigurerequest.x;
524 xwc.y = e->xconfigurerequest.y;
525 xwc.width = e->xconfigurerequest.width;
526 xwc.height = e->xconfigurerequest.height;
527 xwc.border_width = e->xconfigurerequest.border_width;
528 xwc.sibling = e->xconfigurerequest.above;
529 xwc.stack_mode = e->xconfigurerequest.detail;
531 /* we are not to be held responsible if someone sends us an
533 xerror_set_ignore(TRUE);
534 XConfigureWindow(ob_display, window,
535 e->xconfigurerequest.value_mask, &xwc);
536 xerror_set_ignore(FALSE);
539 /* user input (action-bound) events */
540 if (e->type == ButtonPress || e->type == ButtonRelease ||
541 e->type == MotionNotify || e->type == KeyPress ||
542 e->type == KeyRelease)
544 if (menu_frame_visible)
545 event_handle_menu(e);
547 if (!keyboard_process_interactive_grab(e, &client)) {
548 if (moveresize_in_progress) {
551 /* make further actions work on the client being
553 client = moveresize_client;
556 menu_can_hide = FALSE;
557 ob_main_loop_timeout_add(ob_main_loop,
558 config_menu_hide_delay * 1000,
559 menu_hide_delay_func,
560 NULL, g_direct_equal, NULL);
562 if (e->type == ButtonPress || e->type == ButtonRelease ||
563 e->type == MotionNotify) {
564 mouse_event(client, e);
565 } else if (e->type == KeyPress) {
566 keyboard_event((focus_cycle_target ? focus_cycle_target :
572 /* if something happens and it's not from an XEvent, then we don't know
574 event_curtime = CurrentTime;
577 static void event_handle_root(XEvent *e)
583 ob_debug("Another WM has requested to replace us. Exiting.\n");
588 if (e->xclient.format != 32) break;
590 msgtype = e->xclient.message_type;
591 if (msgtype == prop_atoms.net_current_desktop) {
592 guint d = e->xclient.data.l[0];
593 if (d < screen_num_desktops) {
594 event_curtime = e->xclient.data.l[1];
595 ob_debug("SWITCH DESKTOP TIME: %d\n", event_curtime);
596 screen_set_desktop(d);
598 } else if (msgtype == prop_atoms.net_number_of_desktops) {
599 guint d = e->xclient.data.l[0];
601 screen_set_num_desktops(d);
602 } else if (msgtype == prop_atoms.net_showing_desktop) {
603 screen_show_desktop(e->xclient.data.l[0] != 0);
604 } else if (msgtype == prop_atoms.ob_control) {
605 if (e->xclient.data.l[0] == 1)
607 else if (e->xclient.data.l[0] == 2)
612 if (e->xproperty.atom == prop_atoms.net_desktop_names)
613 screen_update_desktop_names();
614 else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
615 screen_update_layout();
617 case ConfigureNotify:
619 XRRUpdateConfiguration(e);
628 static void event_handle_group(ObGroup *group, XEvent *e)
632 g_assert(e->type == PropertyNotify);
634 for (it = group->members; it; it = g_slist_next(it))
635 event_handle_client(it->data, e);
638 void event_enter_client(ObClient *client)
640 g_assert(config_focus_follow);
642 if (client_normal(client) && client_can_focus(client)) {
643 if (config_focus_delay) {
644 ObFocusDelayData *data;
646 ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
648 data = g_new(ObFocusDelayData, 1);
649 data->client = client;
650 data->time = event_curtime;
652 ob_main_loop_timeout_add(ob_main_loop,
655 data, focus_delay_cmp, focus_delay_dest);
657 ObFocusDelayData data;
658 data.client = client;
659 data.time = event_curtime;
660 focus_delay_func(&data);
665 static void event_handle_client(ObClient *client, XEvent *e)
673 case VisibilityNotify:
674 client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
678 /* Wheel buttons don't draw because they are an instant click, so it
679 is a waste of resources to go drawing it. */
680 if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
681 con = frame_context(client, e->xbutton.window);
682 con = mouse_button_frame_context(con, e->xbutton.button);
684 case OB_FRAME_CONTEXT_MAXIMIZE:
685 client->frame->max_press = (e->type == ButtonPress);
686 framerender_frame(client->frame);
688 case OB_FRAME_CONTEXT_CLOSE:
689 client->frame->close_press = (e->type == ButtonPress);
690 framerender_frame(client->frame);
692 case OB_FRAME_CONTEXT_ICONIFY:
693 client->frame->iconify_press = (e->type == ButtonPress);
694 framerender_frame(client->frame);
696 case OB_FRAME_CONTEXT_ALLDESKTOPS:
697 client->frame->desk_press = (e->type == ButtonPress);
698 framerender_frame(client->frame);
700 case OB_FRAME_CONTEXT_SHADE:
701 client->frame->shade_press = (e->type == ButtonPress);
702 framerender_frame(client->frame);
705 /* nothing changes with clicks for any other contexts */
711 con = frame_context(client, e->xcrossing.window);
713 case OB_FRAME_CONTEXT_MAXIMIZE:
714 client->frame->max_hover = FALSE;
715 frame_adjust_state(client->frame);
717 case OB_FRAME_CONTEXT_ALLDESKTOPS:
718 client->frame->desk_hover = FALSE;
719 frame_adjust_state(client->frame);
721 case OB_FRAME_CONTEXT_SHADE:
722 client->frame->shade_hover = FALSE;
723 frame_adjust_state(client->frame);
725 case OB_FRAME_CONTEXT_ICONIFY:
726 client->frame->iconify_hover = FALSE;
727 frame_adjust_state(client->frame);
729 case OB_FRAME_CONTEXT_CLOSE:
730 client->frame->close_hover = FALSE;
731 frame_adjust_state(client->frame);
733 case OB_FRAME_CONTEXT_FRAME:
734 if (keyboard_interactively_grabbed())
736 if (config_focus_follow && config_focus_delay)
737 ob_main_loop_timeout_remove_data(ob_main_loop,
747 gboolean nofocus = FALSE;
749 if (ignore_enter_focus) {
750 ignore_enter_focus--;
754 con = frame_context(client, e->xcrossing.window);
756 case OB_FRAME_CONTEXT_MAXIMIZE:
757 client->frame->max_hover = TRUE;
758 frame_adjust_state(client->frame);
760 case OB_FRAME_CONTEXT_ALLDESKTOPS:
761 client->frame->desk_hover = TRUE;
762 frame_adjust_state(client->frame);
764 case OB_FRAME_CONTEXT_SHADE:
765 client->frame->shade_hover = TRUE;
766 frame_adjust_state(client->frame);
768 case OB_FRAME_CONTEXT_ICONIFY:
769 client->frame->iconify_hover = TRUE;
770 frame_adjust_state(client->frame);
772 case OB_FRAME_CONTEXT_CLOSE:
773 client->frame->close_hover = TRUE;
774 frame_adjust_state(client->frame);
776 case OB_FRAME_CONTEXT_FRAME:
777 if (keyboard_interactively_grabbed())
779 if (e->xcrossing.mode == NotifyGrab ||
780 e->xcrossing.mode == NotifyUngrab)
782 ob_debug_type(OB_DEBUG_FOCUS,
783 "%sNotify mode %d detail %d on %lx IGNORED\n",
784 (e->type == EnterNotify ? "Enter" : "Leave"),
786 e->xcrossing.detail, client?client->window:0);
788 ob_debug_type(OB_DEBUG_FOCUS,
789 "%sNotify mode %d detail %d on %lx, "
790 "focusing window: %d\n",
791 (e->type == EnterNotify ? "Enter" : "Leave"),
793 e->xcrossing.detail, (client?client->window:0),
795 if (!nofocus && config_focus_follow)
796 event_enter_client(client);
804 case ConfigureRequest:
806 while (XCheckTypedWindowEvent(ob_display, client->window,
807 ConfigureRequest, &ce)) {
809 /* XXX if this causes bad things.. we can compress config req's
810 with the same mask. */
811 e->xconfigurerequest.value_mask |=
812 ce.xconfigurerequest.value_mask;
813 if (ce.xconfigurerequest.value_mask & CWX)
814 e->xconfigurerequest.x = ce.xconfigurerequest.x;
815 if (ce.xconfigurerequest.value_mask & CWY)
816 e->xconfigurerequest.y = ce.xconfigurerequest.y;
817 if (ce.xconfigurerequest.value_mask & CWWidth)
818 e->xconfigurerequest.width = ce.xconfigurerequest.width;
819 if (ce.xconfigurerequest.value_mask & CWHeight)
820 e->xconfigurerequest.height = ce.xconfigurerequest.height;
821 if (ce.xconfigurerequest.value_mask & CWBorderWidth)
822 e->xconfigurerequest.border_width =
823 ce.xconfigurerequest.border_width;
824 if (ce.xconfigurerequest.value_mask & CWStackMode)
825 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
828 /* if we are iconic (or shaded (fvwm does this)) ignore the event */
829 if (client->iconic || client->shaded) return;
831 /* resize, then move, as specified in the EWMH section 7.7 */
832 if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
838 if (e->xconfigurerequest.value_mask & CWBorderWidth)
839 client->border_width = e->xconfigurerequest.border_width;
841 x = (e->xconfigurerequest.value_mask & CWX) ?
842 e->xconfigurerequest.x : client->area.x;
843 y = (e->xconfigurerequest.value_mask & CWY) ?
844 e->xconfigurerequest.y : client->area.y;
845 w = (e->xconfigurerequest.value_mask & CWWidth) ?
846 e->xconfigurerequest.width : client->area.width;
847 h = (e->xconfigurerequest.value_mask & CWHeight) ?
848 e->xconfigurerequest.height : client->area.height;
854 client->frame->size.left + client->frame->size.right;
856 client->frame->size.top + client->frame->size.bottom;
857 /* make this rude for size-only changes but not for position
859 gboolean moving = ((e->xconfigurerequest.value_mask & CWX) ||
860 (e->xconfigurerequest.value_mask & CWY));
862 client_find_onscreen(client, &newx, &newy, fw, fh,
864 if (e->xconfigurerequest.value_mask & CWX)
866 if (e->xconfigurerequest.value_mask & CWY)
870 switch (client->gravity) {
871 case NorthEastGravity:
873 corner = OB_CORNER_TOPRIGHT;
875 case SouthWestGravity:
877 corner = OB_CORNER_BOTTOMLEFT;
879 case SouthEastGravity:
880 corner = OB_CORNER_BOTTOMRIGHT;
882 default: /* NorthWest, Static, etc */
883 corner = OB_CORNER_TOPLEFT;
886 client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
890 if (e->xconfigurerequest.value_mask & CWStackMode) {
891 switch (e->xconfigurerequest.detail) {
894 /* Apps are so rude. And this is totally disconnected from
895 activation/focus. Bleh. */
896 /*client_lower(client);*/
902 /* Apps are so rude. And this is totally disconnected from
903 activation/focus. Bleh. */
904 /*client_raise(client);*/
910 if (client->ignore_unmaps) {
911 client->ignore_unmaps--;
914 ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
916 client->window, e->xunmap.event, e->xunmap.from_configure,
917 client->ignore_unmaps);
918 client_unmanage(client);
921 ob_debug("DestroyNotify for window 0x%x\n", client->window);
922 client_unmanage(client);
925 /* this is when the client is first taken captive in the frame */
926 if (e->xreparent.parent == client->frame->plate) break;
929 This event is quite rare and is usually handled in unmapHandler.
930 However, if the window is unmapped when the reparent event occurs,
931 the window manager never sees it because an unmap event is not sent
932 to an already unmapped window.
935 /* we don't want the reparent event, put it back on the stack for the
936 X server to deal with after we unmanage the window */
937 XPutBackEvent(ob_display, e);
939 ob_debug("ReparentNotify for window 0x%x\n", client->window);
940 client_unmanage(client);
943 ob_debug("MapRequest for 0x%lx\n", client->window);
944 if (!client->iconic) break; /* this normally doesn't happen, but if it
945 does, we don't want it!
946 it can happen now when the window is on
947 another desktop, but we still don't
949 client_activate(client, FALSE, TRUE);
952 /* validate cuz we query stuff off the client here */
953 if (!client_validate(client)) break;
955 if (e->xclient.format != 32) return;
957 msgtype = e->xclient.message_type;
958 if (msgtype == prop_atoms.wm_change_state) {
959 /* compress changes into a single change */
960 while (XCheckTypedWindowEvent(ob_display, client->window,
962 /* XXX: it would be nice to compress ALL messages of a
963 type, not just messages in a row without other
964 message types between. */
965 if (ce.xclient.message_type != msgtype) {
966 XPutBackEvent(ob_display, &ce);
969 e->xclient = ce.xclient;
971 client_set_wm_state(client, e->xclient.data.l[0]);
972 } else if (msgtype == prop_atoms.net_wm_desktop) {
973 /* compress changes into a single change */
974 while (XCheckTypedWindowEvent(ob_display, client->window,
976 /* XXX: it would be nice to compress ALL messages of a
977 type, not just messages in a row without other
978 message types between. */
979 if (ce.xclient.message_type != msgtype) {
980 XPutBackEvent(ob_display, &ce);
983 e->xclient = ce.xclient;
985 if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
986 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
987 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
989 } else if (msgtype == prop_atoms.net_wm_state) {
990 /* can't compress these */
991 ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
992 (e->xclient.data.l[0] == 0 ? "Remove" :
993 e->xclient.data.l[0] == 1 ? "Add" :
994 e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
995 e->xclient.data.l[1], e->xclient.data.l[2],
997 client_set_state(client, e->xclient.data.l[0],
998 e->xclient.data.l[1], e->xclient.data.l[2]);
999 } else if (msgtype == prop_atoms.net_close_window) {
1000 ob_debug("net_close_window for 0x%lx\n", client->window);
1001 client_close(client);
1002 } else if (msgtype == prop_atoms.net_active_window) {
1003 ob_debug("net_active_window for 0x%lx source=%s\n",
1005 (e->xclient.data.l[0] == 0 ? "unknown" :
1006 (e->xclient.data.l[0] == 1 ? "application" :
1007 (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1008 /* XXX make use of data.l[2] ! */
1009 event_curtime = e->xclient.data.l[1];
1010 client_activate(client, FALSE,
1011 (e->xclient.data.l[0] == 0 ||
1012 e->xclient.data.l[0] == 2));
1013 } else if (msgtype == prop_atoms.net_wm_moveresize) {
1014 ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1015 client->window, e->xclient.data.l[2]);
1016 if ((Atom)e->xclient.data.l[2] ==
1017 prop_atoms.net_wm_moveresize_size_topleft ||
1018 (Atom)e->xclient.data.l[2] ==
1019 prop_atoms.net_wm_moveresize_size_top ||
1020 (Atom)e->xclient.data.l[2] ==
1021 prop_atoms.net_wm_moveresize_size_topright ||
1022 (Atom)e->xclient.data.l[2] ==
1023 prop_atoms.net_wm_moveresize_size_right ||
1024 (Atom)e->xclient.data.l[2] ==
1025 prop_atoms.net_wm_moveresize_size_right ||
1026 (Atom)e->xclient.data.l[2] ==
1027 prop_atoms.net_wm_moveresize_size_bottomright ||
1028 (Atom)e->xclient.data.l[2] ==
1029 prop_atoms.net_wm_moveresize_size_bottom ||
1030 (Atom)e->xclient.data.l[2] ==
1031 prop_atoms.net_wm_moveresize_size_bottomleft ||
1032 (Atom)e->xclient.data.l[2] ==
1033 prop_atoms.net_wm_moveresize_size_left ||
1034 (Atom)e->xclient.data.l[2] ==
1035 prop_atoms.net_wm_moveresize_move ||
1036 (Atom)e->xclient.data.l[2] ==
1037 prop_atoms.net_wm_moveresize_size_keyboard ||
1038 (Atom)e->xclient.data.l[2] ==
1039 prop_atoms.net_wm_moveresize_move_keyboard) {
1041 moveresize_start(client, e->xclient.data.l[0],
1042 e->xclient.data.l[1], e->xclient.data.l[3],
1043 e->xclient.data.l[2]);
1045 else if ((Atom)e->xclient.data.l[2] ==
1046 prop_atoms.net_wm_moveresize_cancel)
1047 moveresize_end(TRUE);
1048 } else if (msgtype == prop_atoms.net_moveresize_window) {
1049 gint oldg = client->gravity;
1050 gint tmpg, x, y, w, h;
1052 if (e->xclient.data.l[0] & 0xff)
1053 tmpg = e->xclient.data.l[0] & 0xff;
1057 if (e->xclient.data.l[0] & 1 << 8)
1058 x = e->xclient.data.l[1];
1061 if (e->xclient.data.l[0] & 1 << 9)
1062 y = e->xclient.data.l[2];
1065 if (e->xclient.data.l[0] & 1 << 10)
1066 w = e->xclient.data.l[3];
1068 w = client->area.width;
1069 if (e->xclient.data.l[0] & 1 << 11)
1070 h = e->xclient.data.l[4];
1072 h = client->area.height;
1073 client->gravity = tmpg;
1079 client->frame->size.left + client->frame->size.right;
1081 client->frame->size.top + client->frame->size.bottom;
1082 client_find_onscreen(client, &newx, &newy, fw, fh,
1083 client_normal(client));
1084 if (e->xclient.data.l[0] & 1 << 8)
1086 if (e->xclient.data.l[0] & 1 << 9)
1090 client_configure(client, OB_CORNER_TOPLEFT,
1091 x, y, w, h, FALSE, TRUE);
1093 client->gravity = oldg;
1096 case PropertyNotify:
1097 /* validate cuz we query stuff off the client here */
1098 if (!client_validate(client)) break;
1100 /* compress changes to a single property into a single change */
1101 while (XCheckTypedWindowEvent(ob_display, client->window,
1105 /* XXX: it would be nice to compress ALL changes to a property,
1106 not just changes in a row without other props between. */
1108 a = ce.xproperty.atom;
1109 b = e->xproperty.atom;
1113 if ((a == prop_atoms.net_wm_name ||
1114 a == prop_atoms.wm_name ||
1115 a == prop_atoms.net_wm_icon_name ||
1116 a == prop_atoms.wm_icon_name)
1118 (b == prop_atoms.net_wm_name ||
1119 b == prop_atoms.wm_name ||
1120 b == prop_atoms.net_wm_icon_name ||
1121 b == prop_atoms.wm_icon_name)) {
1124 if (a == prop_atoms.net_wm_icon &&
1125 b == prop_atoms.net_wm_icon)
1128 XPutBackEvent(ob_display, &ce);
1132 msgtype = e->xproperty.atom;
1133 if (msgtype == XA_WM_NORMAL_HINTS) {
1134 client_update_normal_hints(client);
1135 /* normal hints can make a window non-resizable */
1136 client_setup_decor_and_functions(client);
1137 } else if (msgtype == XA_WM_HINTS) {
1138 client_update_wmhints(client);
1139 } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1140 client_update_transient_for(client);
1141 client_get_type(client);
1142 /* type may have changed, so update the layer */
1143 client_calc_layer(client);
1144 client_setup_decor_and_functions(client);
1145 } else if (msgtype == prop_atoms.net_wm_name ||
1146 msgtype == prop_atoms.wm_name ||
1147 msgtype == prop_atoms.net_wm_icon_name ||
1148 msgtype == prop_atoms.wm_icon_name) {
1149 client_update_title(client);
1150 } else if (msgtype == prop_atoms.wm_class) {
1151 client_update_class(client);
1152 } else if (msgtype == prop_atoms.wm_protocols) {
1153 client_update_protocols(client);
1154 client_setup_decor_and_functions(client);
1156 else if (msgtype == prop_atoms.net_wm_strut) {
1157 client_update_strut(client);
1159 else if (msgtype == prop_atoms.net_wm_icon) {
1160 client_update_icons(client);
1162 else if (msgtype == prop_atoms.net_wm_user_time) {
1163 client_update_user_time(client);
1165 else if (msgtype == prop_atoms.sm_client_id) {
1166 client_update_sm_client_id(client);
1171 if (extensions_shape && e->type == extensions_shape_event_basep) {
1172 client->shaped = ((XShapeEvent*)e)->shaped;
1173 frame_adjust_shape(client->frame);
1179 static void event_handle_dock(ObDock *s, XEvent *e)
1183 if (e->xbutton.button == 1)
1184 stacking_raise(DOCK_AS_WINDOW(s));
1185 else if (e->xbutton.button == 2)
1186 stacking_lower(DOCK_AS_WINDOW(s));
1197 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1201 dock_app_drag(app, &e->xmotion);
1204 if (app->ignore_unmaps) {
1205 app->ignore_unmaps--;
1208 dock_remove(app, TRUE);
1211 dock_remove(app, FALSE);
1213 case ReparentNotify:
1214 dock_remove(app, FALSE);
1216 case ConfigureNotify:
1217 dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1222 ObMenuFrame* find_active_menu()
1225 ObMenuFrame *ret = NULL;
1227 for (it = menu_frame_visible; it; it = g_list_next(it)) {
1236 ObMenuFrame* find_active_or_last_menu()
1238 ObMenuFrame *ret = NULL;
1240 ret = find_active_menu();
1241 if (!ret && menu_frame_visible)
1242 ret = menu_frame_visible->data;
1246 static void event_handle_menu(XEvent *ev)
1249 ObMenuEntryFrame *e;
1253 if (menu_can_hide) {
1254 if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1255 ev->xbutton.y_root)))
1256 menu_entry_frame_execute(e, ev->xbutton.state,
1259 menu_frame_hide_all();
1263 if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1264 if (e->ignore_enters)
1267 menu_frame_select(e->frame, e);
1271 if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1272 (f = find_active_menu()) && f->selected == e &&
1273 e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1275 menu_frame_select(e->frame, NULL);
1278 if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1279 ev->xmotion.y_root)))
1280 menu_frame_select(e->frame, e);
1283 if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1284 menu_frame_hide_all();
1285 else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1287 if ((f = find_active_menu()))
1288 menu_entry_frame_execute(f->selected, ev->xkey.state,
1290 } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1292 if ((f = find_active_or_last_menu()) && f->parent)
1293 menu_frame_select(f, NULL);
1294 } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1296 if ((f = find_active_or_last_menu()) && f->child)
1297 menu_frame_select_next(f->child);
1298 } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1300 if ((f = find_active_or_last_menu()))
1301 menu_frame_select_previous(f);
1302 } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1304 if ((f = find_active_or_last_menu()))
1305 menu_frame_select_next(f);
1311 static gboolean menu_hide_delay_func(gpointer data)
1313 menu_can_hide = TRUE;
1314 return FALSE; /* no repeat */
1317 static void focus_delay_dest(gpointer data)
1322 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1324 const ObFocusDelayData *f1 = d1;
1325 return f1->client == d2;
1328 static gboolean focus_delay_func(gpointer data)
1330 ObFocusDelayData *d = data;
1331 Time old = event_curtime;
1333 event_curtime = d->time;
1334 if (focus_client != d->client) {
1335 if (client_focus(d->client) && config_focus_raise)
1336 client_raise(d->client);
1338 event_curtime = old;
1339 return FALSE; /* no repeat */
1342 static void focus_delay_client_dest(ObClient *client, gpointer data)
1344 ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1348 void event_halt_focus_delay()
1350 ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1353 void event_ignore_queued_enters()
1355 GSList *saved = NULL, *it;
1358 XSync(ob_display, FALSE);
1360 /* count the events */
1362 e = g_new(XEvent, 1);
1363 if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1366 win = g_hash_table_lookup(window_map, &e->xany.window);
1367 if (win && WINDOW_IS_CLIENT(win))
1368 ++ignore_enter_focus;
1370 saved = g_slist_append(saved, e);
1376 /* put the events back */
1377 for (it = saved; it; it = g_slist_next(it)) {
1378 XPutBackEvent(ob_display, it->data);
1381 g_slist_free(saved);
1384 gboolean event_time_after(Time t1, Time t2)
1386 g_assert(t1 != CurrentTime);
1387 g_assert(t2 != CurrentTime);
1390 Timestamp values wrap around (after about 49.7 days). The server, given
1391 its current time is represented by timestamp T, always interprets
1392 timestamps from clients by treating half of the timestamp space as being
1393 later in time than T.
1394 - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1397 /* TIME_HALF is half of the number space of a Time type variable */
1398 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1400 if (t2 >= TIME_HALF)
1401 /* t2 is in the second half so t1 might wrap around and be smaller than
1403 return t1 >= t2 || t1 < (t2 + TIME_HALF);
1405 /* t2 is in the first half so t1 has to come after it */
1406 return t1 >= t2 && t1 < (t2 + TIME_HALF);