9 #include "framerender.h"
11 #include "moveresize.h"
13 #include "extensions.h"
18 #include <X11/keysym.h>
19 #include <X11/Xatom.h>
20 #ifdef HAVE_SYS_SELECT_H
21 # include <sys/select.h>
24 static void event_process(XEvent *e);
25 static void event_handle_root(XEvent *e);
26 static void event_handle_client(Client *c, XEvent *e);
27 static void event_handle_menu(Menu *menu, XEvent *e);
29 #define INVALID_FOCUSIN(e) ((e)->xfocus.detail == NotifyInferior || \
30 (e)->xfocus.detail > NotifyNonlinearVirtual)
31 #define INVALID_FOCUSOUT(e) ((e)->xfocus.mode == NotifyGrab || \
32 (e)->xfocus.detail == NotifyInferior || \
33 (e)->xfocus.detail == NotifyAncestor || \
34 (e)->xfocus.detail > NotifyNonlinearVirtual)
36 Time event_lasttime = 0;
38 /*! The value of the mask for the NumLock modifier */
39 unsigned int NumLockMask;
40 /*! The value of the mask for the ScrollLock modifier */
41 unsigned int ScrollLockMask;
42 /*! The key codes for the modifier keys */
43 static XModifierKeymap *modmap;
44 /*! Table of the constant modifier masks */
45 static const int mask_table[] = {
46 ShiftMask, LockMask, ControlMask, Mod1Mask,
47 Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
49 static int mask_table_size;
53 mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
55 /* get lock masks that are defined by the display (not constant) */
56 modmap = XGetModifierMapping(ob_display);
58 if (modmap && modmap->max_keypermod > 0) {
60 const size_t size = mask_table_size * modmap->max_keypermod;
61 /* get the values of the keyboard lock modifiers
62 Note: Caps lock is not retrieved the same way as Scroll and Num
63 lock since it doesn't need to be. */
64 const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
65 const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
68 for (cnt = 0; cnt < size; ++cnt) {
69 if (! modmap->modifiermap[cnt]) continue;
71 if (num_lock == modmap->modifiermap[cnt])
72 NumLockMask = mask_table[cnt / modmap->max_keypermod];
73 if (scroll_lock == modmap->modifiermap[cnt])
74 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
81 XFreeModifiermap(modmap);
90 gboolean had_event = FALSE;
94 There are slightly different event retrieval semantics here for
95 local (or high bandwidth) versus remote (or low bandwidth)
96 connections to the display/Xserver.
99 if (!XPending(ob_display))
103 This XSync allows for far more compression of events, which
104 makes things like Motion events perform far far better. Since
105 it also means network traffic for every event instead of every
106 X events (where X is the number retrieved at a time), it
107 probably should not be used for setups where Openbox is
108 running on a remote/low bandwidth display/Xserver.
110 XSync(ob_display, FALSE);
111 if (!XEventsQueued(ob_display, QueuedAlready))
114 XNextEvent(ob_display, &e);
121 timer_dispatch((GTimeVal**)&wait);
122 x_fd = ConnectionNumber(ob_display);
124 FD_SET(x_fd, &selset);
125 select(x_fd + 1, &selset, NULL, NULL, wait);
129 static Window event_get_window(XEvent *e)
136 window = e->xmap.window;
139 window = e->xunmap.window;
142 window = e->xdestroywindow.window;
144 case ConfigureRequest:
145 window = e->xconfigurerequest.window;
149 if (extensions_xkb && e->type == extensions_xkb_event_basep) {
150 switch (((XkbAnyEvent*)&e)->xkb_type) {
152 window = ((XkbBellNotifyEvent*)&e)->window;
158 window = e->xany.window;
163 static void event_set_lasttime(XEvent *e)
165 /* grab the lasttime and hack up the state */
169 event_lasttime = e->xbutton.time;
172 event_lasttime = e->xkey.time;
175 event_lasttime = e->xkey.time;
178 event_lasttime = e->xmotion.time;
181 event_lasttime = e->xproperty.time;
185 event_lasttime = e->xcrossing.time;
188 event_lasttime = CurrentTime;
193 #define STRIP_MODS(s) \
194 s &= ~(LockMask | NumLockMask | ScrollLockMask), \
195 /* kill off the Button1Mask etc, only want the modifiers */ \
196 s &= (ControlMask | ShiftMask | Mod1Mask | \
197 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
199 static void event_hack_mods(XEvent *e)
207 STRIP_MODS(e->xbutton.state);
210 STRIP_MODS(e->xkey.state);
213 STRIP_MODS(e->xkey.state);
214 /* remove from the state the mask of the modifier being released, if
215 it is a modifier key being released (this is a little ugly..) */
216 kp = modmap->modifiermap;
217 for (i = 0; i < mask_table_size; ++i) {
218 for (k = 0; k < modmap->max_keypermod; ++k) {
219 if (*kp == e->xkey.keycode) { /* found the keycode */
220 /* remove the mask for it */
221 e->xkey.state &= ~mask_table[i];
222 /* cause the first loop to break; */
224 break; /* get outta here! */
231 STRIP_MODS(e->xmotion.state);
232 /* compress events */
235 while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
237 e->xmotion.x_root = ce.xmotion.x_root;
238 e->xmotion.y_root = ce.xmotion.y_root;
245 static gboolean event_ignore(XEvent *e, Client *client)
249 /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
250 because of RevertToPointerRoot. If the focus ends up reverting to
251 pointer root on a workspace change, then the FocusIn event that we
252 want will be of type NotifyAncestor. This situation does not occur
253 for FocusOut, so it is safely ignored there.
255 if (INVALID_FOCUSIN(e) ||
258 g_message("FocusIn on %lx mode %d detail %d IGNORED", e->xfocus.window,
259 e->xfocus.mode, e->xfocus.detail);
261 /* says a client was not found for the event (or a valid FocusIn
264 e->xfocus.window = None;
269 g_message("FocusIn on %lx mode %d detail %d", e->xfocus.window,
270 e->xfocus.mode, e->xfocus.detail);
274 if (INVALID_FOCUSOUT(e)) {
276 g_message("FocusOut on %lx mode %d detail %d IGNORED",
277 e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
283 g_message("FocusOut on %lx mode %d detail %d",
284 e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
287 /* Try process a FocusIn first, and if a legit one isn't found, then
288 do the fallback shiznit. */
291 gboolean fallback = TRUE;
294 if (!XCheckTypedEvent(ob_display, FocusOut, &fe))
295 if (!XCheckTypedEvent(ob_display, FocusIn, &fe))
297 if (fe.type == FocusOut) {
299 g_message("found pending FocusOut");
301 if (!INVALID_FOCUSOUT(&fe)) {
302 /* if there is a VALID FocusOut still coming, don't
303 fallback focus yet, we'll deal with it then */
304 XPutBackEvent(ob_display, &fe);
310 g_message("found pending FocusIn");
312 /* once all the FocusOut's have been dealt with, if there
313 is a FocusIn still left and it is valid, then use it */
315 /* secret magic way of event_process telling us that no
316 client was found for the FocusIn event. ^_^ */
317 if (fe.xfocus.window != None) {
325 g_message("no valid FocusIn and no FocusOut events found, "
328 focus_fallback(Fallback_NoFocus);
334 /* NotifyUngrab occurs when a mouse button is released and the event is
335 caused, like when lowering a window */
336 /* NotifyVirtual occurs when ungrabbing the pointer,
337 NotifyNonlinearVirtual occurs when closing a gtk app's menu */
338 if (e->xcrossing.mode == NotifyGrab ||
339 e->xcrossing.detail == NotifyInferior ||
340 (e->xcrossing.mode == NotifyUngrab &&
341 (e->xcrossing.detail == NotifyVirtual ||
342 e->xcrossing.detail == NotifyNonlinearVirtual))) {
344 g_message("EnterNotify mode %d detail %d on %lx IGNORED",
346 e->xcrossing.detail, client?client->window:0);
351 g_message("EnterNotify mode %d detail %d on %lx", e->xcrossing.mode,
352 e->xcrossing.detail, client?client->window:0);
359 static void event_process(XEvent *e)
365 window = event_get_window(e);
366 if (!(client = g_hash_table_lookup(client_map, &window)))
367 menu = g_hash_table_lookup(menu_map, &window);
368 event_set_lasttime(e);
370 if (event_ignore(e, client))
373 /* deal with it in the kernel */
375 event_handle_menu(menu, e);
378 event_handle_client(client, e);
379 else if (window == ob_root)
380 event_handle_root(e);
381 else if (e->type == MapRequest)
382 client_manage(window);
383 else if (e->type == ConfigureRequest) {
384 /* unhandled configure requests must be used to configure the
388 xwc.x = e->xconfigurerequest.x;
389 xwc.y = e->xconfigurerequest.y;
390 xwc.width = e->xconfigurerequest.width;
391 xwc.height = e->xconfigurerequest.height;
392 xwc.border_width = e->xconfigurerequest.border_width;
393 xwc.sibling = e->xconfigurerequest.above;
394 xwc.stack_mode = e->xconfigurerequest.detail;
396 /* we are not to be held responsible if someone sends us an
398 xerror_set_ignore(TRUE);
399 XConfigureWindow(ob_display, window,
400 e->xconfigurerequest.value_mask, &xwc);
401 xerror_set_ignore(FALSE);
404 if (moveresize_in_progress)
405 if (e->type == MotionNotify || e->type == ButtonRelease ||
406 e->type == ButtonPress ||
407 e->type == KeyPress || e->type == KeyRelease) {
409 return; /* no dispatch! */
412 /* user input (action-bound) events */
414 if (e->type == ButtonPress || e->type == ButtonRelease ||
415 e->type == MotionNotify)
416 mouse_event(e, client);
417 else if (e->type == KeyPress || e->type == KeyRelease)
421 /* dispatch the event to registered handlers */
422 dispatch_x(e, client);
425 static void event_handle_root(XEvent *e)
431 if (e->xclient.format != 32) break;
433 msgtype = e->xclient.message_type;
434 if (msgtype == prop_atoms.net_current_desktop) {
435 unsigned int d = e->xclient.data.l[0];
436 if (d < screen_num_desktops)
437 screen_set_desktop(d);
438 } else if (msgtype == prop_atoms.net_number_of_desktops) {
439 unsigned int d = e->xclient.data.l[0];
441 screen_set_num_desktops(d);
442 } else if (msgtype == prop_atoms.net_showing_desktop) {
443 screen_show_desktop(e->xclient.data.l[0] != 0);
447 if (e->xproperty.atom == prop_atoms.net_desktop_names)
448 screen_update_desktop_names();
449 else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
450 screen_update_layout();
455 static void event_handle_client(Client *client, XEvent *e)
464 switch (frame_context(client, e->xbutton.window)) {
465 case Context_Maximize:
466 client->frame->max_press = (e->type == ButtonPress);
467 framerender_frame(client->frame);
470 client->frame->close_press = (e->type == ButtonPress);
471 framerender_frame(client->frame);
473 case Context_Iconify:
474 client->frame->iconify_press = (e->type == ButtonPress);
475 framerender_frame(client->frame);
477 case Context_AllDesktops:
478 client->frame->desk_press = (e->type == ButtonPress);
479 framerender_frame(client->frame);
482 client->frame->shade_press = (e->type == ButtonPress);
483 framerender_frame(client->frame);
486 /* nothing changes with clicks for any other contexts */
491 focus_set_client(client);
494 g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
497 /* focus state can affect the stacking layer */
498 client_calc_layer(client);
499 frame_adjust_focus(client->frame);
502 if (client_normal(client)) {
503 if (ob_state == State_Starting) {
504 /* move it to the top of the focus order */
505 guint desktop = client->desktop;
506 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
507 focus_order[desktop] = g_list_remove(focus_order[desktop],
509 focus_order[desktop] = g_list_prepend(focus_order[desktop],
511 } else if (config_focus_follow) {
513 g_message("EnterNotify on %lx, focusing window",
516 client_focus(client);
520 case ConfigureRequest:
522 while (XCheckTypedWindowEvent(ob_display, client->window,
523 ConfigureRequest, &ce)) {
525 /* XXX if this causes bad things.. we can compress config req's
526 with the same mask. */
527 e->xconfigurerequest.value_mask |=
528 ce.xconfigurerequest.value_mask;
529 if (ce.xconfigurerequest.value_mask & CWX)
530 e->xconfigurerequest.x = ce.xconfigurerequest.x;
531 if (ce.xconfigurerequest.value_mask & CWY)
532 e->xconfigurerequest.y = ce.xconfigurerequest.y;
533 if (ce.xconfigurerequest.value_mask & CWWidth)
534 e->xconfigurerequest.width = ce.xconfigurerequest.width;
535 if (ce.xconfigurerequest.value_mask & CWHeight)
536 e->xconfigurerequest.height = ce.xconfigurerequest.height;
537 if (ce.xconfigurerequest.value_mask & CWBorderWidth)
538 e->xconfigurerequest.border_width =
539 ce.xconfigurerequest.border_width;
540 if (ce.xconfigurerequest.value_mask & CWStackMode)
541 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
544 /* if we are iconic (or shaded (fvwm does this)) ignore the event */
545 if (client->iconic || client->shaded) return;
547 if (e->xconfigurerequest.value_mask & CWBorderWidth)
548 client->border_width = e->xconfigurerequest.border_width;
550 /* resize, then move, as specified in the EWMH section 7.7 */
551 if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
556 x = (e->xconfigurerequest.value_mask & CWX) ?
557 e->xconfigurerequest.x : client->area.x;
558 y = (e->xconfigurerequest.value_mask & CWY) ?
559 e->xconfigurerequest.y : client->area.y;
560 w = (e->xconfigurerequest.value_mask & CWWidth) ?
561 e->xconfigurerequest.width : client->area.width;
562 h = (e->xconfigurerequest.value_mask & CWHeight) ?
563 e->xconfigurerequest.height : client->area.height;
565 switch (client->gravity) {
566 case NorthEastGravity:
568 corner = Corner_TopRight;
570 case SouthWestGravity:
572 corner = Corner_BottomLeft;
574 case SouthEastGravity:
575 corner = Corner_BottomRight;
577 default: /* NorthWest, Static, etc */
578 corner = Corner_TopLeft;
581 client_configure(client, corner, x, y, w, h, FALSE, FALSE);
584 if (e->xconfigurerequest.value_mask & CWStackMode) {
585 switch (e->xconfigurerequest.detail) {
588 stacking_lower(client);
594 stacking_raise(client);
600 if (client->ignore_unmaps) {
601 client->ignore_unmaps--;
604 client_unmanage(client);
607 client_unmanage(client);
610 /* this is when the client is first taken captive in the frame */
611 if (e->xreparent.parent == client->frame->plate) break;
614 This event is quite rare and is usually handled in unmapHandler.
615 However, if the window is unmapped when the reparent event occurs,
616 the window manager never sees it because an unmap event is not sent
617 to an already unmapped window.
620 /* we don't want the reparent event, put it back on the stack for the
621 X server to deal with after we unmanage the window */
622 XPutBackEvent(ob_display, e);
624 client_unmanage(client);
627 g_message("MapRequest for 0x%lx", client->window);
628 if (!client->iconic) break; /* this normally doesn't happen, but if it
629 does, we don't want it! */
630 if (screen_showing_desktop)
631 screen_show_desktop(FALSE);
632 client_iconify(client, FALSE, TRUE);
633 if (!client->frame->visible)
634 /* if its not visible still, then don't mess with it */
637 client_shade(client, FALSE);
638 client_focus(client);
639 stacking_raise(client);
642 /* validate cuz we query stuff off the client here */
643 if (!client_validate(client)) break;
645 if (e->xclient.format != 32) return;
647 msgtype = e->xclient.message_type;
648 if (msgtype == prop_atoms.wm_change_state) {
649 /* compress changes into a single change */
650 while (XCheckTypedWindowEvent(ob_display, e->type,
651 client->window, &ce)) {
652 /* XXX: it would be nice to compress ALL messages of a
653 type, not just messages in a row without other
654 message types between. */
655 if (ce.xclient.message_type != msgtype) {
656 XPutBackEvent(ob_display, &ce);
659 e->xclient = ce.xclient;
661 client_set_wm_state(client, e->xclient.data.l[0]);
662 } else if (msgtype == prop_atoms.net_wm_desktop) {
663 /* compress changes into a single change */
664 while (XCheckTypedWindowEvent(ob_display, e->type,
665 client->window, &ce)) {
666 /* XXX: it would be nice to compress ALL messages of a
667 type, not just messages in a row without other
668 message types between. */
669 if (ce.xclient.message_type != msgtype) {
670 XPutBackEvent(ob_display, &ce);
673 e->xclient = ce.xclient;
675 if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
676 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
677 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
679 } else if (msgtype == prop_atoms.net_wm_state) {
680 /* can't compress these */
681 g_message("net_wm_state %s %ld %ld for 0x%lx",
682 (e->xclient.data.l[0] == 0 ? "Remove" :
683 e->xclient.data.l[0] == 1 ? "Add" :
684 e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
685 e->xclient.data.l[1], e->xclient.data.l[2],
687 client_set_state(client, e->xclient.data.l[0],
688 e->xclient.data.l[1], e->xclient.data.l[2]);
689 } else if (msgtype == prop_atoms.net_close_window) {
690 g_message("net_close_window for 0x%lx", client->window);
691 client_close(client);
692 } else if (msgtype == prop_atoms.net_active_window) {
693 g_message("net_active_window for 0x%lx", client->window);
694 if (screen_showing_desktop)
695 screen_show_desktop(FALSE);
697 client_iconify(client, FALSE, TRUE);
698 else if (!client->frame->visible)
699 /* if its not visible for other reasons, then don't mess
703 client_shade(client, FALSE);
704 client_focus(client);
705 stacking_raise(client);
706 } else if (msgtype == prop_atoms.net_wm_moveresize) {
707 g_message("net_wm_moveresize for 0x%lx", client->window);
708 if ((Atom)e->xclient.data.l[2] ==
709 prop_atoms.net_wm_moveresize_size_topleft ||
710 (Atom)e->xclient.data.l[2] ==
711 prop_atoms.net_wm_moveresize_size_top ||
712 (Atom)e->xclient.data.l[2] ==
713 prop_atoms.net_wm_moveresize_size_topright ||
714 (Atom)e->xclient.data.l[2] ==
715 prop_atoms.net_wm_moveresize_size_right ||
716 (Atom)e->xclient.data.l[2] ==
717 prop_atoms.net_wm_moveresize_size_right ||
718 (Atom)e->xclient.data.l[2] ==
719 prop_atoms.net_wm_moveresize_size_bottomright ||
720 (Atom)e->xclient.data.l[2] ==
721 prop_atoms.net_wm_moveresize_size_bottom ||
722 (Atom)e->xclient.data.l[2] ==
723 prop_atoms.net_wm_moveresize_size_bottomleft ||
724 (Atom)e->xclient.data.l[2] ==
725 prop_atoms.net_wm_moveresize_size_left ||
726 (Atom)e->xclient.data.l[2] ==
727 prop_atoms.net_wm_moveresize_move ||
728 (Atom)e->xclient.data.l[2] ==
729 prop_atoms.net_wm_moveresize_size_keyboard ||
730 (Atom)e->xclient.data.l[2] ==
731 prop_atoms.net_wm_moveresize_move_keyboard) {
733 moveresize_start(client, e->xclient.data.l[0],
734 e->xclient.data.l[1], e->xclient.data.l[3],
735 e->xclient.data.l[2]);
737 } else if (msgtype == prop_atoms.net_moveresize_window) {
738 int oldg = client->gravity;
739 int tmpg, x, y, w, h;
741 if (e->xclient.data.l[0] & 0xff)
742 tmpg = e->xclient.data.l[0] & 0xff;
746 if (e->xclient.data.l[0] & 1 << 8)
747 x = e->xclient.data.l[1];
750 if (e->xclient.data.l[0] & 1 << 9)
751 y = e->xclient.data.l[2];
754 if (e->xclient.data.l[0] & 1 << 10)
755 w = e->xclient.data.l[3];
758 if (e->xclient.data.l[0] & 1 << 11)
759 h = e->xclient.data.l[4];
762 client->gravity = tmpg;
763 client_configure(client, Corner_TopLeft, x, y, w, h, TRUE, TRUE);
764 client->gravity = oldg;
768 /* validate cuz we query stuff off the client here */
769 if (!client_validate(client)) break;
771 /* compress changes to a single property into a single change */
772 while (XCheckTypedWindowEvent(ob_display, e->type,
773 client->window, &ce)) {
774 /* XXX: it would be nice to compress ALL changes to a property,
775 not just changes in a row without other props between. */
776 if (ce.xproperty.atom != e->xproperty.atom) {
777 XPutBackEvent(ob_display, &ce);
782 msgtype = e->xproperty.atom;
783 if (msgtype == XA_WM_NORMAL_HINTS) {
784 client_update_normal_hints(client);
785 /* normal hints can make a window non-resizable */
786 client_setup_decor_and_functions(client);
788 else if (msgtype == XA_WM_HINTS)
789 client_update_wmhints(client);
790 else if (msgtype == XA_WM_TRANSIENT_FOR) {
791 client_update_transient_for(client);
792 client_get_type(client);
793 /* type may have changed, so update the layer */
794 client_calc_layer(client);
795 client_setup_decor_and_functions(client);
797 else if (msgtype == prop_atoms.net_wm_name ||
798 msgtype == prop_atoms.wm_name)
799 client_update_title(client);
800 else if (msgtype == prop_atoms.net_wm_icon_name ||
801 msgtype == prop_atoms.wm_icon_name)
802 client_update_icon_title(client);
803 else if (msgtype == prop_atoms.wm_class)
804 client_update_class(client);
805 else if (msgtype == prop_atoms.wm_protocols) {
806 client_update_protocols(client);
807 client_setup_decor_and_functions(client);
809 else if (msgtype == prop_atoms.net_wm_strut)
810 client_update_strut(client);
811 else if (msgtype == prop_atoms.net_wm_icon)
812 client_update_icons(client);
813 else if (msgtype == prop_atoms.kwm_win_icon)
814 client_update_kwm_icon(client);
818 if (extensions_shape && e->type == extensions_shape_event_basep) {
819 client->shaped = ((XShapeEvent*)e)->shaped;
820 frame_adjust_shape(client->frame);
826 static void event_handle_menu(Menu *menu, XEvent *e)
830 g_message("EVENT %d", e->type);
833 if (e->xbutton.button == 3)
837 if (!menu->shown) break;
839 /* grab_pointer_window(FALSE, None, menu->frame);*/
841 entry = menu_find_entry(menu, e->xbutton.window);
845 guint ujunk, b, w, h;
846 XGetGeometry(ob_display, e->xbutton.window,
847 &wjunk, &junk, &junk, &w, &h, &b, &ujunk);
848 if (e->xbutton.x >= (signed)-b &&
849 e->xbutton.y >= (signed)-b &&
850 e->xbutton.x < (signed)(w+b) &&
851 e->xbutton.y < (signed)(h+b)) {
852 menu_entry_fire(entry);
858 g_message("enter/leave");
859 entry = menu_find_entry(menu, e->xcrossing.window);
861 entry->hilite = e->type == EnterNotify;
862 menu_entry_render(entry);