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-2007 Dana 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.
33 #include "menuframe.h"
37 #include "focus_cycle.h"
38 #include "moveresize.h"
42 #include "obt/display.h"
44 #include "obt/keyboard.h"
47 #include <X11/Xatom.h>
50 #ifdef HAVE_SYS_SELECT_H
51 # include <sys/select.h>
57 # include <unistd.h> /* for usleep() */
61 #include <X11/ICE/ICElib.h>
78 gulong start; /* inclusive */
79 gulong end; /* inclusive */
82 static void event_process(const XEvent *e, gpointer data);
83 static void event_handle_root(XEvent *e);
84 static gboolean event_handle_menu_input(XEvent *e);
85 static void event_handle_menu(ObMenuFrame *frame, XEvent *e);
86 static gboolean event_handle_prompt(ObPrompt *p, XEvent *e);
87 static void event_handle_dock(ObDock *s, XEvent *e);
88 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
89 static void event_handle_client(ObClient *c, XEvent *e);
90 static gboolean event_handle_user_input(ObClient *client, XEvent *e);
91 static gboolean is_enter_focus_event_ignored(gulong serial);
92 static void event_ignore_enter_range(gulong start, gulong end);
94 static void focus_delay_dest(gpointer data);
95 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
96 static gboolean focus_delay_func(gpointer data);
97 static gboolean unfocus_delay_func(gpointer data);
98 static void focus_delay_client_dest(ObClient *client, gpointer data);
100 Time event_curtime = CurrentTime;
101 Time event_last_user_time = CurrentTime;
102 /*! The serial of the current X event */
104 static gulong event_curserial;
105 static gboolean focus_left_screen = FALSE;
106 /*! A list of ObSerialRanges which are to be ignored for mouse enter events */
107 static GSList *ignore_serials = NULL;
110 static void ice_handler(gint fd, gpointer conn)
113 IceProcessMessages(conn, NULL, &b);
116 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
117 IcePointer *watch_data)
122 fd = IceConnectionNumber(conn);
123 obt_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
125 obt_main_loop_fd_remove(ob_main_loop, fd);
131 void event_startup(gboolean reconfig)
133 if (reconfig) return;
135 obt_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
138 IceAddConnectionWatch(ice_watch, NULL);
142 /* ask to be notified when the keyboard group changes */
143 if (obt_display_extension_xkb)
144 XkbSelectEventDetails(obt_display, XkbUseCoreKbd, XkbStateNotify,
145 XkbGroupStateMask, XkbGroupStateMask);
148 client_add_destroy_notify(focus_delay_client_dest, NULL);
151 void event_shutdown(gboolean reconfig)
153 if (reconfig) return;
156 IceRemoveConnectionWatch(ice_watch, NULL);
159 client_remove_destroy_notify(focus_delay_client_dest);
162 static Window event_get_window(XEvent *e)
169 window = obt_root(ob_screen);
172 window = e->xcreatewindow.window;
175 window = e->xmaprequest.window;
178 window = e->xmap.window;
181 window = e->xunmap.window;
184 window = e->xdestroywindow.window;
186 case ConfigureRequest:
187 window = e->xconfigurerequest.window;
189 case ConfigureNotify:
190 window = e->xconfigure.window;
194 if (obt_display_extension_xkb &&
195 e->type == obt_display_extension_xkb_basep)
197 switch (((XkbAnyEvent*)e)->xkb_type) {
199 window = ((XkbBellNotifyEvent*)e)->window;
206 if (obt_display_extension_sync &&
207 e->type == obt_display_extension_sync_basep + XSyncAlarmNotify)
212 window = e->xany.window;
217 static void event_set_curtime(XEvent *e)
219 Time t = CurrentTime;
221 /* grab the lasttime and hack up the state */
237 t = e->xproperty.time;
241 t = e->xcrossing.time;
245 if (obt_display_extension_sync &&
246 e->type == obt_display_extension_sync_basep + XSyncAlarmNotify)
248 t = ((XSyncAlarmNotifyEvent*)e)->time;
251 /* if more event types are anticipated, get their timestamp
256 /* watch that if we get an event earlier than the last specified user_time,
257 which can happen if the clock goes backwards, we erase the last
258 specified user_time */
259 if (t && event_last_user_time && event_time_after(event_last_user_time, t))
260 event_last_user_time = CurrentTime;
265 static void event_hack_mods(XEvent *e)
268 XkbStateRec xkb_state;
274 e->xbutton.state = obt_keyboard_only_modmasks(e->xbutton.state);
277 e->xkey.state = obt_keyboard_only_modmasks(e->xkey.state);
281 /* keep only the keyboard modifiers. xkb includes other things here.
282 (see XKBProto.pdf document: section 2.2.2) */
283 e->xkey.state &= 0xf;
285 e->xkey.state = obt_keyboard_only_modmasks(e->xkey.state);
286 /* remove from the state the mask of the modifier key being
287 released, if it is a modifier key being released that is */
288 e->xkey.state &= ~obt_keyboard_keycode_to_modmask(e->xkey.keycode);
291 e->xmotion.state = obt_keyboard_only_modmasks(e->xmotion.state);
292 /* compress events */
295 while (XCheckTypedWindowEvent(obt_display, e->xmotion.window,
297 e->xmotion.x = ce.xmotion.x;
298 e->xmotion.y = ce.xmotion.y;
299 e->xmotion.x_root = ce.xmotion.x_root;
300 e->xmotion.y_root = ce.xmotion.y_root;
307 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
309 gint mode = e->xfocus.mode;
310 gint detail = e->xfocus.detail;
311 Window win = e->xany.window;
313 if (e->type == FocusIn) {
314 /* These are ones we never want.. */
316 /* This means focus was given by a keyboard/mouse grab. */
317 if (mode == NotifyGrab)
319 /* This means focus was given back from a keyboard/mouse grab. */
320 if (mode == NotifyUngrab)
323 /* These are the ones we want.. */
325 if (win == obt_root(ob_screen)) {
326 /* If looking for a focus in on a client, then always return
327 FALSE for focus in's to the root window */
330 /* This means focus reverted off of a client */
331 else if (detail == NotifyPointerRoot ||
332 detail == NotifyDetailNone ||
333 detail == NotifyInferior ||
334 /* This means focus got here from another screen */
335 detail == NotifyNonlinear)
341 /* It was on a client, was it a valid one?
342 It's possible to get a FocusIn event for a client that was managed
345 if (in_client_only) {
346 ObWindow *w = window_find(e->xfocus.window);
347 if (!w || !WINDOW_IS_CLIENT(w))
351 /* This means focus reverted to parent from the client (this
352 happens often during iconify animation) */
353 if (detail == NotifyInferior)
357 /* This means focus moved from the root window to a client */
358 if (detail == NotifyVirtual)
360 /* This means focus moved from one client to another */
361 if (detail == NotifyNonlinearVirtual)
367 g_assert(e->type == FocusOut);
369 /* These are ones we never want.. */
371 /* This means focus was taken by a keyboard/mouse grab. */
372 if (mode == NotifyGrab)
374 /* This means focus was grabbed on a window and it was released. */
375 if (mode == NotifyUngrab)
378 /* Focus left the root window revertedto state */
379 if (win == obt_root(ob_screen))
382 /* These are the ones we want.. */
384 /* This means focus moved from a client to the root window */
385 if (detail == NotifyVirtual)
387 /* This means focus moved from one client to another */
388 if (detail == NotifyNonlinearVirtual)
396 static Bool event_look_for_focusin(Display *d, XEvent *e, XPointer arg)
398 return e->type == FocusIn && wanted_focusevent(e, FALSE);
401 static Bool event_look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
403 return e->type == FocusIn && wanted_focusevent(e, TRUE);
406 static void print_focusevent(XEvent *e)
408 gint mode = e->xfocus.mode;
409 gint detail = e->xfocus.detail;
410 Window win = e->xany.window;
411 const gchar *modestr, *detailstr;
414 case NotifyNormal: modestr="NotifyNormal"; break;
415 case NotifyGrab: modestr="NotifyGrab"; break;
416 case NotifyUngrab: modestr="NotifyUngrab"; break;
417 case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break;
418 default: g_assert_not_reached();
421 case NotifyAncestor: detailstr="NotifyAncestor"; break;
422 case NotifyVirtual: detailstr="NotifyVirtual"; break;
423 case NotifyInferior: detailstr="NotifyInferior"; break;
424 case NotifyNonlinear: detailstr="NotifyNonlinear"; break;
425 case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
426 case NotifyPointer: detailstr="NotifyPointer"; break;
427 case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
428 case NotifyDetailNone: detailstr="NotifyDetailNone"; break;
429 default: g_assert_not_reached();
432 if (mode == NotifyGrab || mode == NotifyUngrab)
437 ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s",
438 (e->xfocus.type == FocusIn ? "In" : "Out"),
444 static gboolean event_ignore(XEvent *e, ObClient *client)
449 if (!wanted_focusevent(e, FALSE))
454 if (!wanted_focusevent(e, FALSE))
461 static void event_process(const XEvent *ec, gpointer data)
464 ObEventData *ed = data;
467 ObClient *client = NULL;
469 ObDockApp *dockapp = NULL;
470 ObWindow *obwin = NULL;
471 ObMenuFrame *menu = NULL;
472 ObPrompt *prompt = NULL;
475 /* make a copy we can mangle */
479 window = event_get_window(e);
480 if (window == obt_root(ob_screen))
481 /* don't do any lookups, waste of cpu */;
482 else if ((obwin = window_find(window))) {
483 switch (obwin->type) {
484 case OB_WINDOW_CLASS_DOCK:
485 dock = WINDOW_AS_DOCK(obwin);
487 case OB_WINDOW_CLASS_CLIENT:
488 client = WINDOW_AS_CLIENT(obwin);
489 /* events on clients can be events on prompt windows too */
490 prompt = client->prompt;
492 case OB_WINDOW_CLASS_MENUFRAME:
493 menu = WINDOW_AS_MENUFRAME(obwin);
495 case OB_WINDOW_CLASS_INTERNAL:
496 /* we don't do anything with events directly on these windows */
498 case OB_WINDOW_CLASS_PROMPT:
499 prompt = WINDOW_AS_PROMPT(obwin);
504 dockapp = dock_find_dockapp(window);
506 event_set_curtime(e);
507 event_curserial = e->xany.serial;
509 if (event_ignore(e, client)) {
516 /* deal with it in the kernel */
518 if (e->type == FocusIn) {
520 e->xfocus.detail == NotifyInferior)
522 ob_debug_type(OB_DEBUG_FOCUS,
523 "Focus went to the frame window");
525 focus_left_screen = FALSE;
527 focus_fallback(FALSE, config_focus_under_mouse, TRUE, TRUE);
529 /* We don't get a FocusOut for this case, because it's just moving
530 from our Inferior up to us. This happens when iconifying a
531 window with RevertToParent focus */
532 frame_adjust_focus(client->frame, FALSE);
533 /* focus_set_client(NULL) has already been called */
535 else if (e->xfocus.detail == NotifyPointerRoot ||
536 e->xfocus.detail == NotifyDetailNone ||
537 e->xfocus.detail == NotifyInferior ||
538 e->xfocus.detail == NotifyNonlinear)
542 ob_debug_type(OB_DEBUG_FOCUS,
543 "Focus went to root or pointer root/none");
545 if (e->xfocus.detail == NotifyInferior ||
546 e->xfocus.detail == NotifyNonlinear)
548 focus_left_screen = FALSE;
551 /* If another FocusIn is in the queue then don't fallback yet. This
552 fixes the fun case of:
553 window map -> send focusin
554 window unmap -> get focusout
555 window map -> send focusin
556 get first focus out -> fall back to something (new window
557 hasn't received focus yet, so something else) -> send focusin
558 which means the "something else" is the last thing to get a
559 focusin sent to it, so the new window doesn't end up with focus.
561 But if the other focus in is something like PointerRoot then we
562 still want to fall back.
564 if (XCheckIfEvent(obt_display, &ce, event_look_for_focusin_client,
567 XPutBackEvent(obt_display, &ce);
568 ob_debug_type(OB_DEBUG_FOCUS,
569 " but another FocusIn is coming");
571 /* Focus has been reverted.
573 FocusOut events come after UnmapNotify, so we don't need to
574 worry about focusing an invalid window
577 if (!focus_left_screen)
578 focus_fallback(FALSE, config_focus_under_mouse,
584 ob_debug_type(OB_DEBUG_FOCUS,
585 "Focus went to a window that is already gone");
587 /* If you send focus to a window and then it disappears, you can
588 get the FocusIn for it, after it is unmanaged.
589 Just wait for the next FocusOut/FocusIn pair, but make note that
590 the window that was focused no longer is. */
591 focus_set_client(NULL);
593 else if (client != focus_client) {
594 focus_left_screen = FALSE;
595 frame_adjust_focus(client->frame, TRUE);
596 focus_set_client(client);
597 client_calc_layer(client);
598 client_bring_helper_windows(client);
600 } else if (e->type == FocusOut) {
603 /* Look for the followup FocusIn */
604 if (!XCheckIfEvent(obt_display, &ce, event_look_for_focusin, NULL)) {
605 /* There is no FocusIn, this means focus went to a window that
606 is not being managed, or a window on another screen. */
610 obt_display_ignore_errors(TRUE);
611 if (XGetInputFocus(obt_display, &win, &i) &&
612 XGetGeometry(obt_display, win, &root, &i,&i,&u,&u,&u,&u) &&
613 root != obt_root(ob_screen))
615 ob_debug_type(OB_DEBUG_FOCUS,
616 "Focus went to another screen !");
617 focus_left_screen = TRUE;
620 ob_debug_type(OB_DEBUG_FOCUS,
621 "Focus went to a black hole !");
622 obt_display_ignore_errors(FALSE);
623 /* nothing is focused */
624 focus_set_client(NULL);
626 /* Focus moved, so process the FocusIn event */
627 ObEventData ed = { .ignored = FALSE };
628 event_process(&ce, &ed);
630 /* The FocusIn was ignored, this means it was on a window
631 that isn't a client. */
632 ob_debug_type(OB_DEBUG_FOCUS,
633 "Focus went to an unmanaged window 0x%x !",
635 focus_fallback(TRUE, config_focus_under_mouse, TRUE, TRUE);
639 if (client && client != focus_client) {
640 frame_adjust_focus(client->frame, FALSE);
641 /* focus_set_client(NULL) has already been called in this
642 section or by focus_fallback */
646 event_handle_client(client, e);
648 event_handle_dockapp(dockapp, e);
650 event_handle_dock(dock, e);
652 event_handle_menu(menu, e);
653 else if (window == obt_root(ob_screen))
654 event_handle_root(e);
655 else if (e->type == MapRequest)
656 window_manage(window);
658 else if (obt_display_extension_xkb &&
659 e->type == obt_display_extension_xkb_basep)
661 switch (((XkbAnyEvent*)e)->xkb_type) {
663 /* the effective xkb group changed, so they keyboard layout is now
665 if (((XkbStateNotifyEvent*)e)->changed & XkbGroupStateMask)
666 //ob_reconfigure_keyboard();
672 //kbd = XkbAllocKeyboard();
673 XkbGetState(obt_display, XkbUseCoreKbd, &state);
674 g_print("effective group: %d\n", state.group);
675 g_print("base group: %d\n", state.base_group);
677 //XkbFreeKeyboard(kbd, 0, TRUE);
686 else if (e->type == MappingNotify) {
687 ob_debug("MappingNotify");
690 /* the keyboard map changed, so we might need to listen on a new
692 if (obt_display_extension_xkb) {
693 XkbSelectEvents(obt_display, XkbUseCoreKbd, XkbStateNotifyMask, 0);
694 XRefreshKeyboardMapping(&e->xmapping);
695 XkbSelectEventDetails(obt_display, XkbUseCoreKbd, XkbStateNotify,
696 XkbGroupStateMask, XkbGroupStateMask);
699 XRefreshKeyboardMapping(&e->xmapping);
702 ob_reconfigure_keyboard();
704 else if (e->type == ClientMessage) {
705 /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
706 windows that are not managed yet. */
707 if (e->xclient.message_type ==
708 OBT_PROP_ATOM(NET_REQUEST_FRAME_EXTENTS))
710 /* Pretend to manage the client, getting information used to
711 determine its decorations */
712 ObClient *c = client_fake_manage(e->xclient.window);
715 /* set the frame extents on the window */
716 vals[0] = c->frame->size.left;
717 vals[1] = c->frame->size.right;
718 vals[2] = c->frame->size.top;
719 vals[3] = c->frame->size.bottom;
720 OBT_PROP_SETA32(e->xclient.window, NET_FRAME_EXTENTS,
723 /* Free the pretend client */
724 client_fake_unmanage(c);
727 else if (e->type == ConfigureRequest) {
728 /* unhandled configure requests must be used to configure the
732 xwc.x = e->xconfigurerequest.x;
733 xwc.y = e->xconfigurerequest.y;
734 xwc.width = e->xconfigurerequest.width;
735 xwc.height = e->xconfigurerequest.height;
736 xwc.border_width = e->xconfigurerequest.border_width;
737 xwc.sibling = e->xconfigurerequest.above;
738 xwc.stack_mode = e->xconfigurerequest.detail;
740 /* we are not to be held responsible if someone sends us an
742 obt_display_ignore_errors(TRUE);
743 XConfigureWindow(obt_display, window,
744 e->xconfigurerequest.value_mask, &xwc);
745 obt_display_ignore_errors(FALSE);
748 else if (obt_display_extension_sync &&
749 e->type == obt_display_extension_sync_basep + XSyncAlarmNotify)
751 XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
752 if (se->alarm == moveresize_alarm && moveresize_in_progress)
757 if (e->type == ButtonPress || e->type == ButtonRelease) {
759 static guint pressed = 0;
760 static Window pressed_win = None;
762 /* If the button press was on some non-root window, or was physically
763 on the root window... */
764 if (window != obt_root(ob_screen) ||
765 e->xbutton.subwindow == None ||
766 /* ...or if it is related to the last button press we handled... */
767 pressed == e->xbutton.button ||
768 /* ...or it if it was physically on an openbox
769 internal window... */
770 ((w = window_find(e->xbutton.subwindow)) &&
771 WINDOW_IS_INTERNAL(w)))
772 /* ...then process the event, otherwise ignore it */
774 used = event_handle_user_input(client, e);
776 if (e->type == ButtonPress) {
777 pressed = e->xbutton.button;
778 pressed_win = e->xbutton.subwindow;
782 else if (e->type == KeyPress || e->type == KeyRelease ||
783 e->type == MotionNotify)
784 used = event_handle_user_input(client, e);
787 used = event_handle_prompt(prompt, e);
789 /* if something happens and it's not from an XEvent, then we don't know
791 event_curtime = CurrentTime;
795 static void event_handle_root(XEvent *e)
801 ob_debug("Another WM has requested to replace us. Exiting.");
806 if (e->xclient.format != 32) break;
808 msgtype = e->xclient.message_type;
809 if (msgtype == OBT_PROP_ATOM(NET_CURRENT_DESKTOP)) {
810 guint d = e->xclient.data.l[0];
811 if (d < screen_num_desktops) {
812 event_curtime = e->xclient.data.l[1];
813 if (event_curtime == 0)
814 ob_debug_type(OB_DEBUG_APP_BUGS,
815 "_NET_CURRENT_DESKTOP message is missing "
817 screen_set_desktop(d, TRUE);
819 } else if (msgtype == OBT_PROP_ATOM(NET_NUMBER_OF_DESKTOPS)) {
820 guint d = e->xclient.data.l[0];
821 if (d > 0 && d <= 1000)
822 screen_set_num_desktops(d);
823 } else if (msgtype == OBT_PROP_ATOM(NET_SHOWING_DESKTOP)) {
824 screen_show_desktop(e->xclient.data.l[0] != 0, NULL);
825 } else if (msgtype == OBT_PROP_ATOM(OB_CONTROL)) {
826 ob_debug("OB_CONTROL: %d", e->xclient.data.l[0]);
827 if (e->xclient.data.l[0] == 1)
829 else if (e->xclient.data.l[0] == 2)
831 else if (e->xclient.data.l[0] == 3)
833 } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) {
834 if ((Atom)e->xclient.data.l[0] == OBT_PROP_ATOM(NET_WM_PING))
835 ping_got_pong(e->xclient.data.l[1]);
839 if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_NAMES)) {
840 ob_debug("UPDATE DESKTOP NAMES");
841 screen_update_desktop_names();
843 else if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_LAYOUT))
844 screen_update_layout();
846 case ConfigureNotify:
848 XRRUpdateConfiguration(e);
857 void event_enter_client(ObClient *client)
859 g_assert(config_focus_follow);
861 if (is_enter_focus_event_ignored(event_curserial)) {
862 ob_debug_type(OB_DEBUG_FOCUS, "Ignoring enter event with serial %lu\n"
863 "on client 0x%x", event_curserial, client->window);
867 if (client_enter_focusable(client) && client_can_focus(client)) {
868 if (config_focus_delay) {
869 ObFocusDelayData *data;
871 obt_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
873 data = g_new(ObFocusDelayData, 1);
874 data->client = client;
875 data->time = event_curtime;
876 data->serial = event_curserial;
878 obt_main_loop_timeout_add(ob_main_loop,
879 config_focus_delay * 1000,
881 data, focus_delay_cmp, focus_delay_dest);
883 ObFocusDelayData data;
884 data.client = client;
885 data.time = event_curtime;
886 data.serial = event_curserial;
887 focus_delay_func(&data);
892 void event_leave_client(ObClient *client)
894 g_assert(config_focus_follow);
896 if (is_enter_focus_event_ignored(event_curserial)) {
897 ob_debug_type(OB_DEBUG_FOCUS, "Ignoring leave event with serial %lu\n"
898 "on client 0x%x", event_curserial, client->window);
902 if (client == focus_client) {
903 if (config_focus_delay) {
904 ObFocusDelayData *data;
906 obt_main_loop_timeout_remove(ob_main_loop, unfocus_delay_func);
908 data = g_new(ObFocusDelayData, 1);
909 data->client = client;
910 data->time = event_curtime;
911 data->serial = event_curserial;
913 obt_main_loop_timeout_add(ob_main_loop,
914 config_focus_delay * 1000,
916 data, focus_delay_cmp, focus_delay_dest);
918 ObFocusDelayData data;
919 data.client = client;
920 data.time = event_curtime;
921 data.serial = event_curserial;
922 unfocus_delay_func(&data);
927 static gboolean *context_to_button(ObFrame *f, ObFrameContext con, gboolean press)
931 case OB_FRAME_CONTEXT_MAXIMIZE:
932 return &f->max_press;
933 case OB_FRAME_CONTEXT_CLOSE:
934 return &f->close_press;
935 case OB_FRAME_CONTEXT_ICONIFY:
936 return &f->iconify_press;
937 case OB_FRAME_CONTEXT_ALLDESKTOPS:
938 return &f->desk_press;
939 case OB_FRAME_CONTEXT_SHADE:
940 return &f->shade_press;
946 case OB_FRAME_CONTEXT_MAXIMIZE:
947 return &f->max_hover;
948 case OB_FRAME_CONTEXT_CLOSE:
949 return &f->close_hover;
950 case OB_FRAME_CONTEXT_ICONIFY:
951 return &f->iconify_hover;
952 case OB_FRAME_CONTEXT_ALLDESKTOPS:
953 return &f->desk_hover;
954 case OB_FRAME_CONTEXT_SHADE:
955 return &f->shade_hover;
962 static void compress_client_message_event(XEvent *e, XEvent *ce, Window window,
965 /* compress changes into a single change */
966 while (XCheckTypedWindowEvent(obt_display, window, e->type, ce)) {
967 /* XXX: it would be nice to compress ALL messages of a
968 type, not just messages in a row without other
969 message types between. */
970 if (ce->xclient.message_type != msgtype) {
971 XPutBackEvent(obt_display, ce);
974 e->xclient = ce->xclient;
978 static void event_handle_client(ObClient *client, XEvent *e)
984 static gint px = -1, py = -1;
986 static ObFrameContext pcon = OB_FRAME_CONTEXT_NONE;
990 /* save where the press occured for the first button pressed */
992 pb = e->xbutton.button;
996 pcon = frame_context(client, e->xbutton.window, px, py);
997 pcon = mouse_button_frame_context(pcon, e->xbutton.button,
1001 /* Wheel buttons don't draw because they are an instant click, so it
1002 is a waste of resources to go drawing it.
1003 if the user is doing an interactive thing, or has a menu open then
1004 the mouse is grabbed (possibly) and if we get these events we don't
1005 want to deal with them
1007 if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
1008 !grab_on_keyboard())
1010 /* use where the press occured */
1011 con = frame_context(client, e->xbutton.window, px, py);
1012 con = mouse_button_frame_context(con, e->xbutton.button,
1015 /* button presses on CLIENT_CONTEXTs are not accompanied by a
1016 release because they are Replayed to the client */
1017 if ((e->type == ButtonRelease || CLIENT_CONTEXT(con, client)) &&
1018 e->xbutton.button == pb)
1019 pb = 0, px = py = -1, pcon = OB_FRAME_CONTEXT_NONE;
1021 but = context_to_button(client->frame, con, TRUE);
1023 *but = (e->type == ButtonPress);
1024 frame_adjust_state(client->frame);
1029 /* when there is a grab on the pointer, we won't get enter/leave
1030 notifies, but we still get motion events */
1031 if (grab_on_pointer()) break;
1033 con = frame_context(client, e->xmotion.window,
1034 e->xmotion.x, e->xmotion.y);
1036 case OB_FRAME_CONTEXT_TITLEBAR:
1037 case OB_FRAME_CONTEXT_TLCORNER:
1038 case OB_FRAME_CONTEXT_TRCORNER:
1039 /* we've left the button area inside the titlebar */
1040 if (client->frame->max_hover || client->frame->desk_hover ||
1041 client->frame->shade_hover || client->frame->iconify_hover ||
1042 client->frame->close_hover)
1044 client->frame->max_hover =
1045 client->frame->desk_hover =
1046 client->frame->shade_hover =
1047 client->frame->iconify_hover =
1048 client->frame->close_hover = FALSE;
1049 frame_adjust_state(client->frame);
1053 but = context_to_button(client->frame, con, FALSE);
1054 if (but && !*but && !pb) {
1056 frame_adjust_state(client->frame);
1062 con = frame_context(client, e->xcrossing.window,
1063 e->xcrossing.x, e->xcrossing.y);
1065 case OB_FRAME_CONTEXT_TITLEBAR:
1066 case OB_FRAME_CONTEXT_TLCORNER:
1067 case OB_FRAME_CONTEXT_TRCORNER:
1068 /* we've left the button area inside the titlebar */
1069 client->frame->max_hover =
1070 client->frame->desk_hover =
1071 client->frame->shade_hover =
1072 client->frame->iconify_hover =
1073 client->frame->close_hover = FALSE;
1074 if (e->xcrossing.mode == NotifyGrab) {
1075 client->frame->max_press =
1076 client->frame->desk_press =
1077 client->frame->shade_press =
1078 client->frame->iconify_press =
1079 client->frame->close_press = FALSE;
1082 case OB_FRAME_CONTEXT_FRAME:
1083 /* When the mouse leaves an animating window, don't use the
1084 corresponding enter events. Pretend like the animating window
1085 doesn't even exist..! */
1086 if (frame_iconify_animating(client->frame))
1087 event_end_ignore_all_enters(event_start_ignore_all_enters());
1089 ob_debug_type(OB_DEBUG_FOCUS,
1090 "%sNotify mode %d detail %d on %lx",
1091 (e->type == EnterNotify ? "Enter" : "Leave"),
1093 e->xcrossing.detail, (client?client->window:0));
1094 if (grab_on_keyboard())
1096 if (config_focus_follow &&
1097 /* leave inferior events can happen when the mouse goes onto
1098 the window's border and then into the window before the
1100 e->xcrossing.detail != NotifyInferior)
1102 if (config_focus_delay)
1103 obt_main_loop_timeout_remove_data(ob_main_loop,
1106 if (config_unfocus_leave)
1107 event_leave_client(client);
1111 but = context_to_button(client->frame, con, FALSE);
1114 if (e->xcrossing.mode == NotifyGrab) {
1115 but = context_to_button(client->frame, con, TRUE);
1118 frame_adjust_state(client->frame);
1125 con = frame_context(client, e->xcrossing.window,
1126 e->xcrossing.x, e->xcrossing.y);
1128 case OB_FRAME_CONTEXT_FRAME:
1129 if (grab_on_keyboard())
1131 if (e->xcrossing.mode == NotifyGrab ||
1132 e->xcrossing.mode == NotifyUngrab ||
1133 /*ignore enters when we're already in the window */
1134 e->xcrossing.detail == NotifyInferior)
1136 ob_debug_type(OB_DEBUG_FOCUS,
1137 "%sNotify mode %d detail %d serial %lu on %lx "
1139 (e->type == EnterNotify ? "Enter" : "Leave"),
1141 e->xcrossing.detail,
1142 e->xcrossing.serial,
1143 client?client->window:0);
1146 ob_debug_type(OB_DEBUG_FOCUS,
1147 "%sNotify mode %d detail %d serial %lu on %lx, "
1149 (e->type == EnterNotify ? "Enter" : "Leave"),
1151 e->xcrossing.detail,
1152 e->xcrossing.serial,
1153 (client?client->window:0));
1154 if (config_focus_follow) {
1155 if (config_focus_delay)
1156 obt_main_loop_timeout_remove_data(ob_main_loop,
1159 event_enter_client(client);
1164 but = context_to_button(client->frame, con, FALSE);
1167 if (e->xcrossing.mode == NotifyUngrab) {
1168 but = context_to_button(client->frame, con, TRUE);
1169 *but = (con == pcon);
1171 frame_adjust_state(client->frame);
1177 case ConfigureRequest:
1179 /* dont compress these unless you're going to watch for property
1180 notifies in between (these can change what the configure would
1182 also you can't compress stacking events
1186 gboolean move = FALSE;
1187 gboolean resize = FALSE;
1189 /* get the current area */
1190 RECT_TO_DIMS(client->area, x, y, w, h);
1192 ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
1195 screen_desktop, client->wmstate, client->frame->visible);
1196 ob_debug(" x %d y %d w %d h %d b %d",
1197 x, y, w, h, client->border_width);
1199 if (e->xconfigurerequest.value_mask & CWBorderWidth)
1200 if (client->border_width != e->xconfigurerequest.border_width) {
1201 client->border_width = e->xconfigurerequest.border_width;
1203 /* if the border width is changing then that is the same
1204 as requesting a resize, but we don't actually change
1205 the client's border, so it will change their root
1206 coordinates (since they include the border width) and
1207 we need to a notify then */
1211 if (e->xconfigurerequest.value_mask & CWStackMode) {
1212 ObClient *sibling = NULL;
1213 gulong ignore_start;
1216 /* get the sibling */
1217 if (e->xconfigurerequest.value_mask & CWSibling) {
1219 win = window_find(e->xconfigurerequest.above);
1220 if (win && WINDOW_IS_CLIENT(win) &&
1221 WINDOW_AS_CLIENT(win) != client)
1223 sibling = WINDOW_AS_CLIENT(win);
1226 /* an invalid sibling was specified so don't restack at
1227 all, it won't make sense no matter what we do */
1232 if (!config_focus_under_mouse)
1233 ignore_start = event_start_ignore_all_enters();
1234 stacking_restack_request(client, sibling,
1235 e->xconfigurerequest.detail);
1236 if (!config_focus_under_mouse)
1237 event_end_ignore_all_enters(ignore_start);
1240 /* a stacking change moves the window without resizing */
1244 if ((e->xconfigurerequest.value_mask & CWX) ||
1245 (e->xconfigurerequest.value_mask & CWY) ||
1246 (e->xconfigurerequest.value_mask & CWWidth) ||
1247 (e->xconfigurerequest.value_mask & CWHeight))
1249 /* don't allow clients to move shaded windows (fvwm does this)
1251 if (e->xconfigurerequest.value_mask & CWX) {
1252 if (!client->shaded)
1253 x = e->xconfigurerequest.x;
1256 if (e->xconfigurerequest.value_mask & CWY) {
1257 if (!client->shaded)
1258 y = e->xconfigurerequest.y;
1262 if (e->xconfigurerequest.value_mask & CWWidth) {
1263 w = e->xconfigurerequest.width;
1266 if (e->xconfigurerequest.value_mask & CWHeight) {
1267 h = e->xconfigurerequest.height;
1272 ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
1273 "move %d resize %d",
1274 e->xconfigurerequest.value_mask & CWX, x,
1275 e->xconfigurerequest.value_mask & CWY, y,
1276 e->xconfigurerequest.value_mask & CWWidth, w,
1277 e->xconfigurerequest.value_mask & CWHeight, h,
1280 /* check for broken apps moving to their root position
1282 XXX remove this some day...that would be nice. right now all
1283 kde apps do this when they try activate themselves on another
1284 desktop. eg. open amarok window on desktop 1, switch to desktop
1285 2, click amarok tray icon. it will move by its decoration size.
1287 if (x != client->area.x &&
1288 x == (client->frame->area.x + client->frame->size.left -
1289 (gint)client->border_width) &&
1290 y != client->area.y &&
1291 y == (client->frame->area.y + client->frame->size.top -
1292 (gint)client->border_width) &&
1293 w == client->area.width &&
1294 h == client->area.height)
1296 ob_debug_type(OB_DEBUG_APP_BUGS,
1297 "Application %s is trying to move via "
1298 "ConfigureRequest to it's root window position "
1299 "but it is not using StaticGravity",
1305 /* they still requested a move, so don't change whether a
1306 notify is sent or not */
1312 client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1314 /* if x was not given, then use gravity to figure out the new
1315 x. the reference point should not be moved */
1316 if ((e->xconfigurerequest.value_mask & CWWidth &&
1317 !(e->xconfigurerequest.value_mask & CWX)))
1318 client_gravity_resize_w(client, &x, client->area.width, w);
1320 if ((e->xconfigurerequest.value_mask & CWHeight &&
1321 !(e->xconfigurerequest.value_mask & CWY)))
1322 client_gravity_resize_h(client, &y, client->area.height,h);
1324 client_find_onscreen(client, &x, &y, w, h, FALSE);
1326 ob_debug("Granting ConfigureRequest x %d y %d w %d h %d",
1328 client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
1333 ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1335 client->window, e->xunmap.event, e->xunmap.from_configure,
1336 client->ignore_unmaps);
1337 if (client->ignore_unmaps) {
1338 client->ignore_unmaps--;
1341 client_unmanage(client);
1344 ob_debug("DestroyNotify for window 0x%x", client->window);
1345 client_unmanage(client);
1347 case ReparentNotify:
1348 /* this is when the client is first taken captive in the frame */
1349 if (e->xreparent.parent == client->frame->window) break;
1352 This event is quite rare and is usually handled in unmapHandler.
1353 However, if the window is unmapped when the reparent event occurs,
1354 the window manager never sees it because an unmap event is not sent
1355 to an already unmapped window.
1358 /* we don't want the reparent event, put it back on the stack for the
1359 X server to deal with after we unmanage the window */
1360 XPutBackEvent(obt_display, e);
1362 ob_debug("ReparentNotify for window 0x%x", client->window);
1363 client_unmanage(client);
1366 ob_debug("MapRequest for 0x%lx", client->window);
1367 if (!client->iconic) break; /* this normally doesn't happen, but if it
1368 does, we don't want it!
1369 it can happen now when the window is on
1370 another desktop, but we still don't
1372 client_activate(client, FALSE, FALSE, TRUE, TRUE, TRUE);
1375 /* validate cuz we query stuff off the client here */
1376 if (!client_validate(client)) break;
1378 if (e->xclient.format != 32) return;
1380 msgtype = e->xclient.message_type;
1381 if (msgtype == OBT_PROP_ATOM(WM_CHANGE_STATE)) {
1382 compress_client_message_event(e, &ce, client->window, msgtype);
1383 client_set_wm_state(client, e->xclient.data.l[0]);
1384 } else if (msgtype == OBT_PROP_ATOM(NET_WM_DESKTOP)) {
1385 compress_client_message_event(e, &ce, client->window, msgtype);
1386 if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1387 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1388 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1390 } else if (msgtype == OBT_PROP_ATOM(NET_WM_STATE)) {
1391 gulong ignore_start;
1393 /* can't compress these */
1394 ob_debug("net_wm_state %s %ld %ld for 0x%lx",
1395 (e->xclient.data.l[0] == 0 ? "Remove" :
1396 e->xclient.data.l[0] == 1 ? "Add" :
1397 e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1398 e->xclient.data.l[1], e->xclient.data.l[2],
1401 /* ignore enter events caused by these like ob actions do */
1402 if (!config_focus_under_mouse)
1403 ignore_start = event_start_ignore_all_enters();
1404 client_set_state(client, e->xclient.data.l[0],
1405 e->xclient.data.l[1], e->xclient.data.l[2]);
1406 if (!config_focus_under_mouse)
1407 event_end_ignore_all_enters(ignore_start);
1408 } else if (msgtype == OBT_PROP_ATOM(NET_CLOSE_WINDOW)) {
1409 ob_debug("net_close_window for 0x%lx", client->window);
1410 client_close(client);
1411 } else if (msgtype == OBT_PROP_ATOM(NET_ACTIVE_WINDOW)) {
1412 ob_debug("net_active_window for 0x%lx source=%s",
1414 (e->xclient.data.l[0] == 0 ? "unknown" :
1415 (e->xclient.data.l[0] == 1 ? "application" :
1416 (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1417 /* XXX make use of data.l[2] !? */
1418 if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
1419 event_curtime = e->xclient.data.l[1];
1420 if (e->xclient.data.l[1] == 0)
1421 ob_debug_type(OB_DEBUG_APP_BUGS,
1422 "_NET_ACTIVE_WINDOW message for window %s is"
1423 " missing a timestamp", client->title);
1425 ob_debug_type(OB_DEBUG_APP_BUGS,
1426 "_NET_ACTIVE_WINDOW message for window %s is "
1427 "missing source indication", client->title);
1428 client_activate(client, FALSE, FALSE, TRUE, TRUE,
1429 (e->xclient.data.l[0] == 0 ||
1430 e->xclient.data.l[0] == 2));
1431 } else if (msgtype == OBT_PROP_ATOM(NET_WM_MOVERESIZE)) {
1432 ob_debug("net_wm_moveresize for 0x%lx direction %d",
1433 client->window, e->xclient.data.l[2]);
1434 if ((Atom)e->xclient.data.l[2] ==
1435 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT) ||
1436 (Atom)e->xclient.data.l[2] ==
1437 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOP) ||
1438 (Atom)e->xclient.data.l[2] ==
1439 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPRIGHT) ||
1440 (Atom)e->xclient.data.l[2] ==
1441 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
1442 (Atom)e->xclient.data.l[2] ==
1443 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
1444 (Atom)e->xclient.data.l[2] ==
1445 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT) ||
1446 (Atom)e->xclient.data.l[2] ==
1447 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOM) ||
1448 (Atom)e->xclient.data.l[2] ==
1449 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT) ||
1450 (Atom)e->xclient.data.l[2] ==
1451 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_LEFT) ||
1452 (Atom)e->xclient.data.l[2] ==
1453 OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE) ||
1454 (Atom)e->xclient.data.l[2] ==
1455 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_KEYBOARD) ||
1456 (Atom)e->xclient.data.l[2] ==
1457 OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE_KEYBOARD))
1459 moveresize_start(client, e->xclient.data.l[0],
1460 e->xclient.data.l[1], e->xclient.data.l[3],
1461 e->xclient.data.l[2]);
1463 else if ((Atom)e->xclient.data.l[2] ==
1464 OBT_PROP_ATOM(NET_WM_MOVERESIZE_CANCEL))
1465 moveresize_end(TRUE);
1466 } else if (msgtype == OBT_PROP_ATOM(NET_MOVERESIZE_WINDOW)) {
1467 gint ograv, x, y, w, h;
1469 ograv = client->gravity;
1471 if (e->xclient.data.l[0] & 0xff)
1472 client->gravity = e->xclient.data.l[0] & 0xff;
1474 if (e->xclient.data.l[0] & 1 << 8)
1475 x = e->xclient.data.l[1];
1478 if (e->xclient.data.l[0] & 1 << 9)
1479 y = e->xclient.data.l[2];
1483 if (e->xclient.data.l[0] & 1 << 10) {
1484 w = e->xclient.data.l[3];
1486 /* if x was not given, then use gravity to figure out the new
1487 x. the reference point should not be moved */
1488 if (!(e->xclient.data.l[0] & 1 << 8))
1489 client_gravity_resize_w(client, &x, client->area.width, w);
1492 w = client->area.width;
1494 if (e->xclient.data.l[0] & 1 << 11) {
1495 h = e->xclient.data.l[4];
1498 if (!(e->xclient.data.l[0] & 1 << 9))
1499 client_gravity_resize_h(client, &y, client->area.height,h);
1502 h = client->area.height;
1504 ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)",
1505 e->xclient.data.l[0] & 1 << 8, x,
1506 e->xclient.data.l[0] & 1 << 9, y,
1509 client_find_onscreen(client, &x, &y, w, h, FALSE);
1511 client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1513 client->gravity = ograv;
1514 } else if (msgtype == OBT_PROP_ATOM(NET_RESTACK_WINDOW)) {
1515 if (e->xclient.data.l[0] != 2) {
1516 ob_debug_type(OB_DEBUG_APP_BUGS,
1517 "_NET_RESTACK_WINDOW sent for window %s with "
1518 "invalid source indication %ld",
1519 client->title, e->xclient.data.l[0]);
1521 ObClient *sibling = NULL;
1522 if (e->xclient.data.l[1]) {
1523 ObWindow *win = window_find(e->xclient.data.l[1]);
1524 if (WINDOW_IS_CLIENT(win) &&
1525 WINDOW_AS_CLIENT(win) != client)
1527 sibling = WINDOW_AS_CLIENT(win);
1529 if (sibling == NULL)
1530 ob_debug_type(OB_DEBUG_APP_BUGS,
1531 "_NET_RESTACK_WINDOW sent for window %s "
1532 "with invalid sibling 0x%x",
1533 client->title, e->xclient.data.l[1]);
1535 if (e->xclient.data.l[2] == Below ||
1536 e->xclient.data.l[2] == BottomIf ||
1537 e->xclient.data.l[2] == Above ||
1538 e->xclient.data.l[2] == TopIf ||
1539 e->xclient.data.l[2] == Opposite)
1541 gulong ignore_start;
1543 if (!config_focus_under_mouse)
1544 ignore_start = event_start_ignore_all_enters();
1545 /* just raise, don't activate */
1546 stacking_restack_request(client, sibling,
1547 e->xclient.data.l[2]);
1548 if (!config_focus_under_mouse)
1549 event_end_ignore_all_enters(ignore_start);
1551 /* send a synthetic ConfigureNotify, cuz this is supposed
1552 to be like a ConfigureRequest. */
1553 client_reconfigure(client, TRUE);
1555 ob_debug_type(OB_DEBUG_APP_BUGS,
1556 "_NET_RESTACK_WINDOW sent for window %s "
1557 "with invalid detail %d",
1558 client->title, e->xclient.data.l[2]);
1562 case PropertyNotify:
1563 /* validate cuz we query stuff off the client here */
1564 if (!client_validate(client)) break;
1566 /* compress changes to a single property into a single change */
1567 while (XCheckTypedWindowEvent(obt_display, client->window,
1571 /* XXX: it would be nice to compress ALL changes to a property,
1572 not just changes in a row without other props between. */
1574 a = ce.xproperty.atom;
1575 b = e->xproperty.atom;
1579 if ((a == OBT_PROP_ATOM(NET_WM_NAME) ||
1580 a == OBT_PROP_ATOM(WM_NAME) ||
1581 a == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1582 a == OBT_PROP_ATOM(WM_ICON_NAME))
1584 (b == OBT_PROP_ATOM(NET_WM_NAME) ||
1585 b == OBT_PROP_ATOM(WM_NAME) ||
1586 b == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1587 b == OBT_PROP_ATOM(WM_ICON_NAME))) {
1590 if (a == OBT_PROP_ATOM(NET_WM_ICON) &&
1591 b == OBT_PROP_ATOM(NET_WM_ICON))
1594 XPutBackEvent(obt_display, &ce);
1598 msgtype = e->xproperty.atom;
1599 if (msgtype == XA_WM_NORMAL_HINTS) {
1600 int x, y, w, h, lw, lh;
1602 ob_debug("Update NORMAL hints");
1603 client_update_normal_hints(client);
1604 /* normal hints can make a window non-resizable */
1605 client_setup_decor_and_functions(client, FALSE);
1609 w = client->area.width;
1610 h = client->area.height;
1612 /* apply the new normal hints */
1613 client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1614 /* make sure the window is visible, and if the window is resized
1615 off-screen due to the normal hints changing then this will push
1616 it back onto the screen. */
1617 client_find_onscreen(client, &x, &y, w, h, FALSE);
1619 /* make sure the client's sizes are within its bounds, but don't
1620 make it reply with a configurenotify unless something changed.
1621 emacs will update its normal hints every time it receives a
1623 client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1624 } else if (msgtype == OBT_PROP_ATOM(MOTIF_WM_HINTS)) {
1625 client_get_mwm_hints(client);
1626 /* This can override some mwm hints */
1627 client_get_type_and_transientness(client);
1629 /* Apply the changes to the window */
1630 client_setup_decor_and_functions(client, TRUE);
1631 } else if (msgtype == XA_WM_HINTS) {
1632 client_update_wmhints(client);
1633 } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1634 client_update_transient_for(client);
1635 client_get_type_and_transientness(client);
1636 /* type may have changed, so update the layer */
1637 client_calc_layer(client);
1638 client_setup_decor_and_functions(client, TRUE);
1639 } else if (msgtype == OBT_PROP_ATOM(NET_WM_NAME) ||
1640 msgtype == OBT_PROP_ATOM(WM_NAME) ||
1641 msgtype == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1642 msgtype == OBT_PROP_ATOM(WM_ICON_NAME)) {
1643 client_update_title(client);
1644 } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) {
1645 client_update_protocols(client);
1646 client_setup_decor_and_functions(client, TRUE);
1648 else if (msgtype == OBT_PROP_ATOM(NET_WM_STRUT) ||
1649 msgtype == OBT_PROP_ATOM(NET_WM_STRUT_PARTIAL)) {
1650 client_update_strut(client);
1652 else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON)) {
1653 client_update_icons(client);
1655 else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON_GEOMETRY)) {
1656 client_update_icon_geometry(client);
1658 else if (msgtype == OBT_PROP_ATOM(NET_WM_USER_TIME)) {
1660 if (client == focus_client &&
1661 OBT_PROP_GET32(client->window, NET_WM_USER_TIME, CARDINAL, &t)
1662 && t && !event_time_after(t, e->xproperty.time) &&
1663 (!event_last_user_time ||
1664 event_time_after(t, event_last_user_time)))
1666 event_last_user_time = t;
1670 else if (msgtype == OBT_PROP_ATOM(NET_WM_SYNC_REQUEST_COUNTER)) {
1671 client_update_sync_request_counter(client);
1675 case ColormapNotify:
1676 client_update_colormap(client, e->xcolormap.colormap);
1683 if (obt_display_extension_shape &&
1684 e->type == obt_display_extension_shape_basep)
1686 switch (((XShapeEvent*)e)->kind) {
1689 client->shaped = ((XShapeEvent*)e)->shaped;
1690 kind = ShapeBounding;
1693 client->shaped_input = ((XShapeEvent*)e)->shaped;
1697 g_assert_not_reached();
1699 frame_adjust_shape_kind(client->frame, kind);
1706 static void event_handle_dock(ObDock *s, XEvent *e)
1710 if (e->xbutton.button == 1)
1711 stacking_raise(DOCK_AS_WINDOW(s));
1712 else if (e->xbutton.button == 2)
1713 stacking_lower(DOCK_AS_WINDOW(s));
1719 /* don't hide when moving into a dock app */
1720 if (e->xcrossing.detail != NotifyInferior)
1726 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1730 dock_app_drag(app, &e->xmotion);
1733 if (app->ignore_unmaps) {
1734 app->ignore_unmaps--;
1737 dock_unmanage(app, TRUE);
1740 case ReparentNotify:
1741 dock_unmanage(app, FALSE);
1743 case ConfigureNotify:
1744 dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1749 static ObMenuFrame* find_active_menu(void)
1752 ObMenuFrame *ret = NULL;
1754 for (it = menu_frame_visible; it; it = g_list_next(it)) {
1763 static ObMenuFrame* find_active_or_last_menu(void)
1765 ObMenuFrame *ret = NULL;
1767 ret = find_active_menu();
1768 if (!ret && menu_frame_visible)
1769 ret = menu_frame_visible->data;
1773 static gboolean event_handle_prompt(ObPrompt *p, XEvent *e)
1779 return prompt_mouse_event(p, e);
1782 return prompt_key_event(p, e);
1788 static gboolean event_handle_menu_input(XEvent *ev)
1790 gboolean ret = FALSE;
1792 if (ev->type == ButtonRelease || ev->type == ButtonPress) {
1793 ObMenuEntryFrame *e;
1795 if (menu_hide_delay_reached() &&
1796 (ev->xbutton.button < 4 || ev->xbutton.button > 5))
1798 if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1799 ev->xbutton.y_root)))
1801 if (ev->type == ButtonPress && e->frame->child)
1802 menu_frame_select(e->frame->child, NULL, TRUE);
1803 menu_frame_select(e->frame, e, TRUE);
1804 if (ev->type == ButtonRelease)
1805 menu_entry_frame_execute(e, ev->xbutton.state);
1807 else if (ev->type == ButtonRelease)
1808 menu_frame_hide_all();
1812 else if (ev->type == MotionNotify) {
1814 ObMenuEntryFrame *e;
1816 if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1817 ev->xmotion.y_root)))
1818 if (!(f = find_active_menu()) ||
1820 f->parent == e->frame ||
1821 f->child == e->frame)
1822 menu_frame_select(e->frame, e, FALSE);
1824 else if (ev->type == KeyPress) {
1825 guint keycode, state;
1826 static gunichar unikey;
1829 keycode = ev->xkey.keycode;
1830 state = ev->xkey.state;
1832 frame = find_active_or_last_menu();
1834 g_assert_not_reached(); /* there is no active menu */
1836 /* Allow control while going thru the menu */
1837 else if (ev->type == KeyPress && (state & ~ControlMask) == 0) {
1838 frame->got_press = TRUE;
1840 if (ob_keycode_match(keycode, OB_KEY_ESCAPE)) {
1841 menu_frame_hide_all();
1845 else if (ob_keycode_match(keycode, OB_KEY_LEFT)) {
1846 /* Left goes to the parent menu */
1847 if (frame->parent) {
1848 /* remove focus from the child */
1849 menu_frame_select(frame, NULL, TRUE);
1850 /* and put it in the parent */
1851 menu_frame_select(frame->parent, frame->parent->selected,
1857 else if (ob_keycode_match(keycode, OB_KEY_RIGHT)) {
1858 /* Right goes to the selected submenu */
1859 if (frame->selected->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1861 /* make sure it is visible */
1862 menu_frame_select(frame, frame->selected, TRUE);
1863 menu_frame_select_next(frame->child);
1868 else if (ob_keycode_match(keycode, OB_KEY_UP)) {
1869 menu_frame_select_previous(frame);
1873 else if (ob_keycode_match(keycode, OB_KEY_DOWN)) {
1874 menu_frame_select_next(frame);
1878 else if (ob_keycode_match(keycode, OB_KEY_HOME)) {
1879 menu_frame_select_first(frame);
1883 else if (ob_keycode_match(keycode, OB_KEY_END)) {
1884 menu_frame_select_last(frame);
1888 /* Remember the last keypress */
1890 unikey = obt_keyboard_keypress_to_unichar(&ev->xkey);
1895 /* Use KeyRelease events for running things so that the key release
1896 doesn't get sent to the focused application.
1898 Allow ControlMask only, and don't bother if the menu is empty */
1899 else if (ev->type == KeyRelease && (state & ~ControlMask) == 0 &&
1900 frame->entries && frame->got_press)
1902 if (ob_keycode_match(keycode, OB_KEY_RETURN)) {
1903 /* Enter runs the active item or goes into the submenu.
1904 Control-Enter runs it without closing the menu. */
1906 menu_frame_select_next(frame->child);
1907 else if (frame->selected)
1908 menu_entry_frame_execute(frame->selected, state);
1913 /* keyboard accelerator shortcuts. (if it was a valid key) */
1914 else if (unikey != 0) {
1917 ObMenuEntryFrame *found = NULL;
1918 guint num_found = 0;
1920 /* start after the selected one */
1921 start = frame->entries;
1922 if (frame->selected) {
1923 for (it = start; frame->selected != it->data;
1924 it = g_list_next(it))
1925 g_assert(it != NULL); /* nothing was selected? */
1926 /* next with wraparound */
1927 start = g_list_next(it);
1928 if (start == NULL) start = frame->entries;
1933 ObMenuEntryFrame *e = it->data;
1934 gunichar entrykey = 0;
1936 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1937 entrykey = e->entry->data.normal.shortcut;
1938 else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1939 entrykey = e->entry->data.submenu.submenu->shortcut;
1941 if (unikey == entrykey) {
1942 if (found == NULL) found = e;
1946 /* next with wraparound */
1947 it = g_list_next(it);
1948 if (it == NULL) it = frame->entries;
1949 } while (it != start);
1952 if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1955 menu_frame_select(frame, found, TRUE);
1956 usleep(50000); /* highlight the item for a short bit so
1957 the user can see what happened */
1958 menu_entry_frame_execute(found, state);
1960 menu_frame_select(frame, found, TRUE);
1962 menu_frame_select_next(frame->child);
1974 static Bool event_look_for_menu_enter(Display *d, XEvent *ev, XPointer arg)
1976 ObMenuFrame *f = (ObMenuFrame*)arg;
1977 ObMenuEntryFrame *e;
1978 return ev->type == EnterNotify &&
1979 (e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1980 !e->ignore_enters && e->frame == f;
1983 static void event_handle_menu(ObMenuFrame *frame, XEvent *ev)
1986 ObMenuEntryFrame *e;
1990 if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1991 if (e->ignore_enters)
1993 else if (!(f = find_active_menu()) ||
1995 f->parent == e->frame ||
1996 f->child == e->frame)
1997 menu_frame_select(e->frame, e, FALSE);
2001 /*ignore leaves when we're already in the window */
2002 if (ev->xcrossing.detail == NotifyInferior)
2005 if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)))
2009 /* check if an EnterNotify event is coming, and if not, then select
2010 nothing in the menu */
2011 if (XCheckIfEvent(obt_display, &ce, event_look_for_menu_enter,
2012 (XPointer)e->frame))
2013 XPutBackEvent(obt_display, &ce);
2015 menu_frame_select(e->frame, NULL, FALSE);
2021 static gboolean event_handle_user_input(ObClient *client, XEvent *e)
2023 g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
2024 e->type == MotionNotify || e->type == KeyPress ||
2025 e->type == KeyRelease);
2027 if (menu_frame_visible) {
2028 if (event_handle_menu_input(e))
2029 /* don't use the event if the menu used it, but if the menu
2030 didn't use it and it's a keypress that is bound, it will
2031 close the menu and be used */
2035 /* if the keyboard interactive action uses the event then dont
2036 use it for bindings. likewise is moveresize uses the event. */
2037 if (actions_interactive_input_event(e) || moveresize_event(e))
2040 if (moveresize_in_progress)
2041 /* make further actions work on the client being
2043 client = moveresize_client;
2045 if (e->type == ButtonPress ||
2046 e->type == ButtonRelease ||
2047 e->type == MotionNotify)
2049 /* the frame may not be "visible" but they can still click on it
2050 in the case where it is animating before disappearing */
2051 if (!client || !frame_iconify_animating(client->frame))
2052 return mouse_event(client, e);
2054 return keyboard_event((focus_cycle_target ? focus_cycle_target :
2055 (client ? client : focus_client)), e);
2060 static void focus_delay_dest(gpointer data)
2065 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
2067 const ObFocusDelayData *f1 = d1;
2068 return f1->client == d2;
2071 static gboolean focus_delay_func(gpointer data)
2073 ObFocusDelayData *d = data;
2074 Time old = event_curtime;
2076 event_curtime = d->time;
2077 event_curserial = d->serial;
2078 if (client_focus(d->client) && config_focus_raise)
2079 stacking_raise(CLIENT_AS_WINDOW(d->client));
2080 event_curtime = old;
2081 return FALSE; /* no repeat */
2084 static gboolean unfocus_delay_func(gpointer data)
2086 ObFocusDelayData *d = data;
2087 Time old = event_curtime;
2089 event_curtime = d->time;
2090 event_curserial = d->serial;
2092 event_curtime = old;
2093 return FALSE; /* no repeat */
2096 static void focus_delay_client_dest(ObClient *client, gpointer data)
2098 obt_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
2100 obt_main_loop_timeout_remove_data(ob_main_loop, unfocus_delay_func,
2104 void event_halt_focus_delay(void)
2106 /* ignore all enter events up till the event which caused this to occur */
2107 if (event_curserial) event_ignore_enter_range(1, event_curserial);
2108 obt_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
2109 obt_main_loop_timeout_remove(ob_main_loop, unfocus_delay_func);
2112 gulong event_start_ignore_all_enters(void)
2114 return NextRequest(obt_display);
2117 static void event_ignore_enter_range(gulong start, gulong end)
2121 g_assert(start != 0);
2124 r = g_new(ObSerialRange, 1);
2127 ignore_serials = g_slist_prepend(ignore_serials, r);
2129 ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu",
2132 /* increment the serial so we don't ignore events we weren't meant to */
2133 OBT_PROP_ERASE(screen_support_win, MOTIF_WM_HINTS);
2136 void event_end_ignore_all_enters(gulong start)
2138 /* Use (NextRequest-1) so that we ignore up to the current serial only.
2139 Inside event_ignore_enter_range, we increment the serial by one, but if
2140 we ignore that serial too, then any enter events generated by mouse
2141 movement will be ignored until we create some further network traffic.
2142 Instead ignore up to NextRequest-1, then when we increment the serial,
2143 we will be *past* the range of ignored serials */
2144 event_ignore_enter_range(start, NextRequest(obt_display)-1);
2147 static gboolean is_enter_focus_event_ignored(gulong serial)
2151 for (it = ignore_serials; it; it = next) {
2152 ObSerialRange *r = it->data;
2154 next = g_slist_next(it);
2156 if ((glong)(serial - r->end) > 0) {
2158 ignore_serials = g_slist_delete_link(ignore_serials, it);
2161 else if ((glong)(serial - r->start) >= 0)
2167 void event_cancel_all_key_grabs(void)
2169 if (actions_interactive_act_running()) {
2170 actions_interactive_cancel_act();
2171 ob_debug("KILLED interactive action");
2173 else if (menu_frame_visible) {
2174 menu_frame_hide_all();
2175 ob_debug("KILLED open menus");
2177 else if (moveresize_in_progress) {
2178 moveresize_end(TRUE);
2179 ob_debug("KILLED interactive moveresize");
2181 else if (grab_on_keyboard()) {
2183 ob_debug("KILLED active grab on keyboard");
2186 ungrab_passive_key();
2188 XSync(obt_display, FALSE);
2191 gboolean event_time_after(guint32 t1, guint32 t2)
2193 g_assert(t1 != CurrentTime);
2194 g_assert(t2 != CurrentTime);
2197 Timestamp values wrap around (after about 49.7 days). The server, given
2198 its current time is represented by timestamp T, always interprets
2199 timestamps from clients by treating half of the timestamp space as being
2200 later in time than T.
2201 - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
2204 /* TIME_HALF is not half of the number space of a Time type variable.
2205 * Rather, it is half the number space of a timestamp value, which is
2206 * always 32 bits. */
2207 #define TIME_HALF (guint32)(1 << 31)
2209 if (t2 >= TIME_HALF)
2210 /* t2 is in the second half so t1 might wrap around and be smaller than
2212 return t1 >= t2 || t1 < (t2 + TIME_HALF);
2214 /* t2 is in the first half so t1 has to come after it */
2215 return t1 >= t2 && t1 < (t2 + TIME_HALF);
2218 Time event_get_server_time(void)
2220 /* Generate a timestamp */
2223 XChangeProperty(obt_display, screen_support_win,
2224 OBT_PROP_ATOM(WM_CLASS), OBT_PROP_ATOM(STRING),
2225 8, PropModeAppend, NULL, 0);
2226 XWindowEvent(obt_display, screen_support_win, PropertyChangeMask, &event);
2227 return event.xproperty.time;