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.
35 #include "menuframe.h"
41 #include "focus_cycle.h"
42 #include "moveresize.h"
45 #include "extensions.h"
46 #include "translate.h"
50 #include <X11/Xatom.h>
53 #ifdef HAVE_SYS_SELECT_H
54 # include <sys/select.h>
60 # include <unistd.h> /* for usleep() */
63 # include <X11/XKBlib.h>
67 #include <X11/ICE/ICElib.h>
84 gulong start; /* inclusive */
85 gulong end; /* inclusive */
88 static void event_process(const XEvent *e, gpointer data);
89 static void event_handle_root(XEvent *e);
90 static gboolean event_handle_menu_keyboard(XEvent *e);
91 static gboolean event_handle_menu(XEvent *e);
92 static gboolean event_handle_prompt(ObPrompt *p, XEvent *e);
93 static void event_handle_dock(ObDock *s, XEvent *e);
94 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
95 static void event_handle_client(ObClient *c, XEvent *e);
96 static void event_handle_user_input(ObClient *client, XEvent *e);
97 static gboolean is_enter_focus_event_ignored(gulong serial);
98 static void event_ignore_enter_range(gulong start, gulong end);
100 static void focus_delay_dest(gpointer data);
101 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
102 static gboolean focus_delay_func(gpointer data);
103 static void focus_delay_client_dest(ObClient *client, gpointer data);
105 Time event_curtime = CurrentTime;
106 Time event_last_user_time = CurrentTime;
107 /*! The serial of the current X event */
109 static gulong event_curserial;
110 static gboolean focus_left_screen = FALSE;
111 /*! A list of ObSerialRanges which are to be ignored for mouse enter events */
112 static GSList *ignore_serials = NULL;
115 static void ice_handler(gint fd, gpointer conn)
118 IceProcessMessages(conn, NULL, &b);
121 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
122 IcePointer *watch_data)
127 fd = IceConnectionNumber(conn);
128 ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
130 ob_main_loop_fd_remove(ob_main_loop, fd);
136 void event_startup(gboolean reconfig)
138 if (reconfig) return;
140 ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
143 IceAddConnectionWatch(ice_watch, NULL);
146 client_add_destroy_notify(focus_delay_client_dest, NULL);
149 void event_shutdown(gboolean reconfig)
151 if (reconfig) return;
154 IceRemoveConnectionWatch(ice_watch, NULL);
157 client_remove_destroy_notify(focus_delay_client_dest);
160 static Window event_get_window(XEvent *e)
167 window = RootWindow(ob_display, ob_screen);
170 window = e->xmap.window;
173 window = e->xunmap.window;
176 window = e->xdestroywindow.window;
178 case ConfigureRequest:
179 window = e->xconfigurerequest.window;
181 case ConfigureNotify:
182 window = e->xconfigure.window;
186 if (extensions_xkb && e->type == extensions_xkb_event_basep) {
187 switch (((XkbAnyEvent*)e)->xkb_type) {
189 window = ((XkbBellNotifyEvent*)e)->window;
196 if (extensions_sync &&
197 e->type == extensions_sync_event_basep + XSyncAlarmNotify)
202 window = e->xany.window;
207 static void event_set_curtime(XEvent *e)
209 Time t = CurrentTime;
211 /* grab the lasttime and hack up the state */
227 t = e->xproperty.time;
231 t = e->xcrossing.time;
235 if (extensions_sync &&
236 e->type == extensions_sync_event_basep + XSyncAlarmNotify)
238 t = ((XSyncAlarmNotifyEvent*)e)->time;
241 /* if more event types are anticipated, get their timestamp
246 /* watch that if we get an event earlier than the last specified user_time,
247 which can happen if the clock goes backwards, we erase the last
248 specified user_time */
249 if (t && event_last_user_time && event_time_after(event_last_user_time, t))
250 event_last_user_time = CurrentTime;
255 static void event_hack_mods(XEvent *e)
260 e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
263 e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
267 /* keep only the keyboard modifiers. xkb includes other things here.
268 (see XKBProto.pdf document: section 2.2.2) */
269 e->xkey.state &= 0xf;
271 e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
272 /* remove from the state the mask of the modifier key being
273 released, if it is a modifier key being released that is */
274 e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
277 e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
278 /* compress events */
281 while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
283 e->xmotion.x = ce.xmotion.x;
284 e->xmotion.y = ce.xmotion.y;
285 e->xmotion.x_root = ce.xmotion.x_root;
286 e->xmotion.y_root = ce.xmotion.y_root;
293 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
295 gint mode = e->xfocus.mode;
296 gint detail = e->xfocus.detail;
297 Window win = e->xany.window;
299 if (e->type == FocusIn) {
300 /* These are ones we never want.. */
302 /* This means focus was given by a keyboard/mouse grab. */
303 if (mode == NotifyGrab)
305 /* This means focus was given back from a keyboard/mouse grab. */
306 if (mode == NotifyUngrab)
309 /* These are the ones we want.. */
311 if (win == RootWindow(ob_display, ob_screen)) {
312 /* If looking for a focus in on a client, then always return
313 FALSE for focus in's to the root window */
316 /* This means focus reverted off of a client */
317 else if (detail == NotifyPointerRoot ||
318 detail == NotifyDetailNone ||
319 detail == NotifyInferior ||
320 /* This means focus got here from another screen */
321 detail == NotifyNonlinear)
327 /* It was on a client, was it a valid one?
328 It's possible to get a FocusIn event for a client that was managed
331 if (in_client_only) {
332 ObWindow *w = g_hash_table_lookup(window_map, &e->xfocus.window);
333 if (!w || !WINDOW_IS_CLIENT(w))
337 /* This means focus reverted to parent from the client (this
338 happens often during iconify animation) */
339 if (detail == NotifyInferior)
343 /* This means focus moved from the root window to a client */
344 if (detail == NotifyVirtual)
346 /* This means focus moved from one client to another */
347 if (detail == NotifyNonlinearVirtual)
353 g_assert(e->type == FocusOut);
355 /* These are ones we never want.. */
357 /* This means focus was taken by a keyboard/mouse grab. */
358 if (mode == NotifyGrab)
360 /* This means focus was grabbed on a window and it was released. */
361 if (mode == NotifyUngrab)
364 /* Focus left the root window revertedto state */
365 if (win == RootWindow(ob_display, ob_screen))
368 /* These are the ones we want.. */
370 /* This means focus moved from a client to the root window */
371 if (detail == NotifyVirtual)
373 /* This means focus moved from one client to another */
374 if (detail == NotifyNonlinearVirtual)
382 static Bool event_look_for_focusin(Display *d, XEvent *e, XPointer arg)
384 return e->type == FocusIn && wanted_focusevent(e, FALSE);
387 static Bool event_look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
389 return e->type == FocusIn && wanted_focusevent(e, TRUE);
392 static void print_focusevent(XEvent *e)
394 gint mode = e->xfocus.mode;
395 gint detail = e->xfocus.detail;
396 Window win = e->xany.window;
397 const gchar *modestr, *detailstr;
400 case NotifyNormal: modestr="NotifyNormal"; break;
401 case NotifyGrab: modestr="NotifyGrab"; break;
402 case NotifyUngrab: modestr="NotifyUngrab"; break;
403 case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break;
406 case NotifyAncestor: detailstr="NotifyAncestor"; break;
407 case NotifyVirtual: detailstr="NotifyVirtual"; break;
408 case NotifyInferior: detailstr="NotifyInferior"; break;
409 case NotifyNonlinear: detailstr="NotifyNonlinear"; break;
410 case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
411 case NotifyPointer: detailstr="NotifyPointer"; break;
412 case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
413 case NotifyDetailNone: detailstr="NotifyDetailNone"; break;
416 if (mode == NotifyGrab || mode == NotifyUngrab)
421 ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s\n",
422 (e->xfocus.type == FocusIn ? "In" : "Out"),
428 static gboolean event_ignore(XEvent *e, ObClient *client)
433 if (!wanted_focusevent(e, FALSE))
438 if (!wanted_focusevent(e, FALSE))
445 static void event_process(const XEvent *ec, gpointer data)
448 ObClient *client = NULL;
450 ObDockApp *dockapp = NULL;
451 ObWindow *obwin = NULL;
453 ObEventData *ed = data;
454 ObPrompt *prompt = NULL;
456 /* make a copy we can mangle */
460 window = event_get_window(e);
461 if ((obwin = g_hash_table_lookup(window_map, &window))) {
462 switch (obwin->type) {
464 dock = WINDOW_AS_DOCK(obwin);
467 dockapp = WINDOW_AS_DOCKAPP(obwin);
470 client = WINDOW_AS_CLIENT(obwin);
471 /* events on clients can be events on prompt windows too */
472 prompt = client->prompt;
475 /* not to be used for events */
476 g_assert_not_reached();
478 case Window_Internal:
479 /* we don't do anything with events directly on these windows */
482 prompt = WINDOW_AS_PROMPT(obwin);
487 event_set_curtime(e);
488 event_curserial = e->xany.serial;
490 if (event_ignore(e, client)) {
497 /* deal with it in the kernel */
499 if (menu_frame_visible &&
500 (e->type == EnterNotify || e->type == LeaveNotify))
502 /* crossing events for menu */
503 event_handle_menu(e);
504 } else if (e->type == FocusIn) {
506 e->xfocus.detail == NotifyInferior)
508 ob_debug_type(OB_DEBUG_FOCUS,
509 "Focus went to the frame window");
511 focus_left_screen = FALSE;
513 focus_fallback(FALSE, config_focus_under_mouse, TRUE, TRUE);
515 /* We don't get a FocusOut for this case, because it's just moving
516 from our Inferior up to us. This happens when iconifying a
517 window with RevertToParent focus */
518 frame_adjust_focus(client->frame, FALSE);
519 /* focus_set_client(NULL) has already been called */
521 else if (e->xfocus.detail == NotifyPointerRoot ||
522 e->xfocus.detail == NotifyDetailNone ||
523 e->xfocus.detail == NotifyInferior ||
524 e->xfocus.detail == NotifyNonlinear)
528 ob_debug_type(OB_DEBUG_FOCUS,
529 "Focus went to root or pointer root/none\n");
531 if (e->xfocus.detail == NotifyInferior ||
532 e->xfocus.detail == NotifyNonlinear)
534 focus_left_screen = FALSE;
537 /* If another FocusIn is in the queue then don't fallback yet. This
538 fixes the fun case of:
539 window map -> send focusin
540 window unmap -> get focusout
541 window map -> send focusin
542 get first focus out -> fall back to something (new window
543 hasn't received focus yet, so something else) -> send focusin
544 which means the "something else" is the last thing to get a
545 focusin sent to it, so the new window doesn't end up with focus.
547 But if the other focus in is something like PointerRoot then we
548 still want to fall back.
550 if (XCheckIfEvent(ob_display, &ce, event_look_for_focusin_client,
553 XPutBackEvent(ob_display, &ce);
554 ob_debug_type(OB_DEBUG_FOCUS,
555 " but another FocusIn is coming\n");
557 /* Focus has been reverted.
559 FocusOut events come after UnmapNotify, so we don't need to
560 worry about focusing an invalid window
563 if (!focus_left_screen)
564 focus_fallback(FALSE, config_focus_under_mouse,
570 ob_debug_type(OB_DEBUG_FOCUS,
571 "Focus went to a window that is already gone\n");
573 /* If you send focus to a window and then it disappears, you can
574 get the FocusIn for it, after it is unmanaged.
575 Just wait for the next FocusOut/FocusIn pair, but make note that
576 the window that was focused no longer is. */
577 focus_set_client(NULL);
579 else if (client != focus_client) {
580 focus_left_screen = FALSE;
581 frame_adjust_focus(client->frame, TRUE);
582 focus_set_client(client);
583 client_calc_layer(client);
584 client_bring_helper_windows(client);
586 } else if (e->type == FocusOut) {
589 /* Look for the followup FocusIn */
590 if (!XCheckIfEvent(ob_display, &ce, event_look_for_focusin, NULL)) {
591 /* There is no FocusIn, this means focus went to a window that
592 is not being managed, or a window on another screen. */
596 xerror_set_ignore(TRUE);
597 if (XGetInputFocus(ob_display, &win, &i) != 0 &&
598 XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 &&
599 root != RootWindow(ob_display, ob_screen))
601 ob_debug_type(OB_DEBUG_FOCUS,
602 "Focus went to another screen !\n");
603 focus_left_screen = TRUE;
606 ob_debug_type(OB_DEBUG_FOCUS,
607 "Focus went to a black hole !\n");
608 xerror_set_ignore(FALSE);
609 /* nothing is focused */
610 focus_set_client(NULL);
612 /* Focus moved, so process the FocusIn event */
613 ObEventData ed = { .ignored = FALSE };
614 event_process(&ce, &ed);
616 /* The FocusIn was ignored, this means it was on a window
617 that isn't a client. */
618 ob_debug_type(OB_DEBUG_FOCUS,
619 "Focus went to an unmanaged window 0x%x !\n",
621 focus_fallback(TRUE, config_focus_under_mouse, TRUE, TRUE);
625 if (client && client != focus_client) {
626 frame_adjust_focus(client->frame, FALSE);
627 /* focus_set_client(NULL) has already been called in this
628 section or by focus_fallback */
632 event_handle_client(client, e);
634 event_handle_dockapp(dockapp, e);
636 event_handle_dock(dock, e);
637 else if (window == RootWindow(ob_display, ob_screen))
638 event_handle_root(e);
639 else if (e->type == MapRequest)
640 client_manage(window, NULL);
641 else if (e->type == MappingNotify) {
642 /* keyboard layout changes for modifier mapping changes. reload the
643 modifier map, and rebind all the key bindings as appropriate */
644 ob_debug("Keyboard map changed. Reloading keyboard bindings.\n");
645 ob_set_state(OB_STATE_RECONFIGURING);
646 modkeys_shutdown(TRUE);
647 modkeys_startup(TRUE);
649 ob_set_state(OB_STATE_RUNNING);
651 else if (e->type == ClientMessage) {
652 /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
653 windows that are not managed yet. */
654 if (e->xclient.message_type == prop_atoms.net_request_frame_extents) {
655 /* Pretend to manage the client, getting information used to
656 determine its decorations */
657 ObClient *c = client_fake_manage(e->xclient.window);
660 /* set the frame extents on the window */
661 vals[0] = c->frame->size.left;
662 vals[1] = c->frame->size.right;
663 vals[2] = c->frame->size.top;
664 vals[3] = c->frame->size.bottom;
665 PROP_SETA32(e->xclient.window, net_frame_extents,
668 /* Free the pretend client */
669 client_fake_unmanage(c);
672 else if (e->type == ConfigureRequest) {
673 /* unhandled configure requests must be used to configure the
677 xwc.x = e->xconfigurerequest.x;
678 xwc.y = e->xconfigurerequest.y;
679 xwc.width = e->xconfigurerequest.width;
680 xwc.height = e->xconfigurerequest.height;
681 xwc.border_width = e->xconfigurerequest.border_width;
682 xwc.sibling = e->xconfigurerequest.above;
683 xwc.stack_mode = e->xconfigurerequest.detail;
685 /* we are not to be held responsible if someone sends us an
687 xerror_set_ignore(TRUE);
688 XConfigureWindow(ob_display, window,
689 e->xconfigurerequest.value_mask, &xwc);
690 xerror_set_ignore(FALSE);
693 else if (extensions_sync &&
694 e->type == extensions_sync_event_basep + XSyncAlarmNotify)
696 XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
697 if (se->alarm == moveresize_alarm && moveresize_in_progress)
702 if (prompt && event_handle_prompt(prompt, e))
704 else if (e->type == ButtonPress || e->type == ButtonRelease) {
705 /* If the button press was on some non-root window, or was physically
706 on the root window, then process it */
707 if (window != RootWindow(ob_display, ob_screen) ||
708 e->xbutton.subwindow == None)
710 event_handle_user_input(client, e);
712 /* Otherwise only process it if it was physically on an openbox
717 if ((w = g_hash_table_lookup(window_map, &e->xbutton.subwindow)) &&
718 WINDOW_IS_INTERNAL(w))
720 event_handle_user_input(client, e);
724 else if (e->type == KeyPress || e->type == KeyRelease ||
725 e->type == MotionNotify)
726 event_handle_user_input(client, e);
728 /* if something happens and it's not from an XEvent, then we don't know
730 event_curtime = CurrentTime;
734 static void event_handle_root(XEvent *e)
740 ob_debug("Another WM has requested to replace us. Exiting.\n");
745 if (e->xclient.format != 32) break;
747 msgtype = e->xclient.message_type;
748 if (msgtype == prop_atoms.net_current_desktop) {
749 guint d = e->xclient.data.l[0];
750 if (d < screen_num_desktops) {
751 event_curtime = e->xclient.data.l[1];
752 if (event_curtime == 0)
753 ob_debug_type(OB_DEBUG_APP_BUGS,
754 "_NET_CURRENT_DESKTOP message is missing "
756 screen_set_desktop(d, TRUE);
758 } else if (msgtype == prop_atoms.net_number_of_desktops) {
759 guint d = e->xclient.data.l[0];
760 if (d > 0 && d <= 1000)
761 screen_set_num_desktops(d);
762 } else if (msgtype == prop_atoms.net_showing_desktop) {
763 screen_show_desktop(e->xclient.data.l[0] != 0, NULL);
764 } else if (msgtype == prop_atoms.ob_control) {
765 ob_debug("OB_CONTROL: %d\n", e->xclient.data.l[0]);
766 if (e->xclient.data.l[0] == 1)
768 else if (e->xclient.data.l[0] == 2)
770 else if (e->xclient.data.l[0] == 3)
772 } else if (msgtype == prop_atoms.wm_protocols) {
773 if ((Atom)e->xclient.data.l[0] == prop_atoms.net_wm_ping)
774 ping_got_pong(e->xclient.data.l[1]);
778 if (e->xproperty.atom == prop_atoms.net_desktop_names) {
779 ob_debug("UPDATE DESKTOP NAMES\n");
780 screen_update_desktop_names();
782 else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
783 screen_update_layout();
785 case ConfigureNotify:
787 XRRUpdateConfiguration(e);
796 void event_enter_client(ObClient *client)
798 g_assert(config_focus_follow);
800 if (is_enter_focus_event_ignored(event_curserial)) {
801 ob_debug_type(OB_DEBUG_FOCUS, "Ignoring enter event with serial %lu\n"
802 "on client 0x%x", event_curserial, client->window);
806 if (client_enter_focusable(client) && client_can_focus(client)) {
807 if (config_focus_delay) {
808 ObFocusDelayData *data;
810 ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
812 data = g_new(ObFocusDelayData, 1);
813 data->client = client;
814 data->time = event_curtime;
815 data->serial = event_curserial;
817 ob_main_loop_timeout_add(ob_main_loop,
818 config_focus_delay * 1000,
820 data, focus_delay_cmp, focus_delay_dest);
822 ObFocusDelayData data;
823 data.client = client;
824 data.time = event_curtime;
825 data.serial = event_curserial;
826 focus_delay_func(&data);
831 static gboolean *context_to_button(ObFrame *f, ObFrameContext con, gboolean press)
835 case OB_FRAME_CONTEXT_MAXIMIZE:
836 return &f->max_press;
837 case OB_FRAME_CONTEXT_CLOSE:
838 return &f->close_press;
839 case OB_FRAME_CONTEXT_ICONIFY:
840 return &f->iconify_press;
841 case OB_FRAME_CONTEXT_ALLDESKTOPS:
842 return &f->desk_press;
843 case OB_FRAME_CONTEXT_SHADE:
844 return &f->shade_press;
850 case OB_FRAME_CONTEXT_MAXIMIZE:
851 return &f->max_hover;
852 case OB_FRAME_CONTEXT_CLOSE:
853 return &f->close_hover;
854 case OB_FRAME_CONTEXT_ICONIFY:
855 return &f->iconify_hover;
856 case OB_FRAME_CONTEXT_ALLDESKTOPS:
857 return &f->desk_hover;
858 case OB_FRAME_CONTEXT_SHADE:
859 return &f->shade_hover;
866 static void compress_client_message_event(XEvent *e, XEvent *ce, Window window,
869 /* compress changes into a single change */
870 while (XCheckTypedWindowEvent(ob_display, window, e->type, ce)) {
871 /* XXX: it would be nice to compress ALL messages of a
872 type, not just messages in a row without other
873 message types between. */
874 if (ce->xclient.message_type != msgtype) {
875 XPutBackEvent(ob_display, ce);
878 e->xclient = ce->xclient;
882 static void event_handle_client(ObClient *client, XEvent *e)
888 static gint px = -1, py = -1;
890 static ObFrameContext pcon = OB_FRAME_CONTEXT_NONE;
894 /* save where the press occured for the first button pressed */
896 pb = e->xbutton.button;
900 pcon = frame_context(client, e->xbutton.window, px, py);
901 pcon = mouse_button_frame_context(pcon, e->xbutton.button,
905 /* Wheel buttons don't draw because they are an instant click, so it
906 is a waste of resources to go drawing it.
907 if the user is doing an interactive thing, or has a menu open then
908 the mouse is grabbed (possibly) and if we get these events we don't
909 want to deal with them
911 if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
914 /* use where the press occured */
915 con = frame_context(client, e->xbutton.window, px, py);
916 con = mouse_button_frame_context(con, e->xbutton.button,
919 /* button presses on CLIENT_CONTEXTs are not accompanied by a
920 release because they are Replayed to the client */
921 if ((e->type == ButtonRelease || CLIENT_CONTEXT(con, client)) &&
922 e->xbutton.button == pb)
923 pb = 0, px = py = -1, pcon = OB_FRAME_CONTEXT_NONE;
925 but = context_to_button(client->frame, con, TRUE);
927 *but = (e->type == ButtonPress);
928 frame_adjust_state(client->frame);
933 /* when there is a grab on the pointer, we won't get enter/leave
934 notifies, but we still get motion events */
935 if (grab_on_pointer()) break;
937 con = frame_context(client, e->xmotion.window,
938 e->xmotion.x, e->xmotion.y);
940 case OB_FRAME_CONTEXT_TITLEBAR:
941 case OB_FRAME_CONTEXT_TLCORNER:
942 case OB_FRAME_CONTEXT_TRCORNER:
943 /* we've left the button area inside the titlebar */
944 if (client->frame->max_hover || client->frame->desk_hover ||
945 client->frame->shade_hover || client->frame->iconify_hover ||
946 client->frame->close_hover)
948 client->frame->max_hover =
949 client->frame->desk_hover =
950 client->frame->shade_hover =
951 client->frame->iconify_hover =
952 client->frame->close_hover = FALSE;
953 frame_adjust_state(client->frame);
957 but = context_to_button(client->frame, con, FALSE);
958 if (but && !*but && !pb) {
960 frame_adjust_state(client->frame);
966 con = frame_context(client, e->xcrossing.window,
967 e->xcrossing.x, e->xcrossing.y);
969 case OB_FRAME_CONTEXT_TITLEBAR:
970 case OB_FRAME_CONTEXT_TLCORNER:
971 case OB_FRAME_CONTEXT_TRCORNER:
972 /* we've left the button area inside the titlebar */
973 client->frame->max_hover =
974 client->frame->desk_hover =
975 client->frame->shade_hover =
976 client->frame->iconify_hover =
977 client->frame->close_hover = FALSE;
978 if (e->xcrossing.mode == NotifyGrab) {
979 client->frame->max_press =
980 client->frame->desk_press =
981 client->frame->shade_press =
982 client->frame->iconify_press =
983 client->frame->close_press = FALSE;
986 case OB_FRAME_CONTEXT_FRAME:
987 /* When the mouse leaves an animating window, don't use the
988 corresponding enter events. Pretend like the animating window
989 doesn't even exist..! */
990 if (frame_iconify_animating(client->frame))
991 event_end_ignore_all_enters(event_start_ignore_all_enters());
993 ob_debug_type(OB_DEBUG_FOCUS,
994 "%sNotify mode %d detail %d on %lx\n",
995 (e->type == EnterNotify ? "Enter" : "Leave"),
997 e->xcrossing.detail, (client?client->window:0));
998 if (grab_on_keyboard())
1000 if (config_focus_follow && config_focus_delay &&
1001 /* leave inferior events can happen when the mouse goes onto
1002 the window's border and then into the window before the
1004 e->xcrossing.detail != NotifyInferior)
1006 ob_main_loop_timeout_remove_data(ob_main_loop,
1012 but = context_to_button(client->frame, con, FALSE);
1015 if (e->xcrossing.mode == NotifyGrab) {
1016 but = context_to_button(client->frame, con, TRUE);
1019 frame_adjust_state(client->frame);
1026 con = frame_context(client, e->xcrossing.window,
1027 e->xcrossing.x, e->xcrossing.y);
1029 case OB_FRAME_CONTEXT_FRAME:
1030 if (grab_on_keyboard())
1032 if (e->xcrossing.mode == NotifyGrab ||
1033 e->xcrossing.mode == NotifyUngrab ||
1034 /*ignore enters when we're already in the window */
1035 e->xcrossing.detail == NotifyInferior)
1037 ob_debug_type(OB_DEBUG_FOCUS,
1038 "%sNotify mode %d detail %d serial %lu on %lx "
1040 (e->type == EnterNotify ? "Enter" : "Leave"),
1042 e->xcrossing.detail,
1043 e->xcrossing.serial,
1044 client?client->window:0);
1047 ob_debug_type(OB_DEBUG_FOCUS,
1048 "%sNotify mode %d detail %d serial %lu on %lx, "
1049 "focusing window\n",
1050 (e->type == EnterNotify ? "Enter" : "Leave"),
1052 e->xcrossing.detail,
1053 e->xcrossing.serial,
1054 (client?client->window:0));
1055 if (config_focus_follow)
1056 event_enter_client(client);
1060 but = context_to_button(client->frame, con, FALSE);
1063 if (e->xcrossing.mode == NotifyUngrab) {
1064 but = context_to_button(client->frame, con, TRUE);
1065 *but = (con == pcon);
1067 frame_adjust_state(client->frame);
1073 case ConfigureRequest:
1075 /* dont compress these unless you're going to watch for property
1076 notifies in between (these can change what the configure would
1078 also you can't compress stacking events
1082 gboolean move = FALSE;
1083 gboolean resize = FALSE;
1085 /* get the current area */
1086 RECT_TO_DIMS(client->area, x, y, w, h);
1088 ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
1090 " x %d y %d w %d h %d b %d\n",
1092 screen_desktop, client->wmstate, client->frame->visible,
1093 x, y, w, h, client->border_width);
1095 if (e->xconfigurerequest.value_mask & CWBorderWidth)
1096 if (client->border_width != e->xconfigurerequest.border_width) {
1097 client->border_width = e->xconfigurerequest.border_width;
1099 /* if the border width is changing then that is the same
1100 as requesting a resize, but we don't actually change
1101 the client's border, so it will change their root
1102 coordinates (since they include the border width) and
1103 we need to a notify then */
1107 if (e->xconfigurerequest.value_mask & CWStackMode) {
1108 ObClient *sibling = NULL;
1109 gulong ignore_start;
1112 /* get the sibling */
1113 if (e->xconfigurerequest.value_mask & CWSibling) {
1115 win = g_hash_table_lookup(window_map,
1116 &e->xconfigurerequest.above);
1117 if (win && WINDOW_IS_CLIENT(win) &&
1118 WINDOW_AS_CLIENT(win) != client)
1120 sibling = WINDOW_AS_CLIENT(win);
1123 /* an invalid sibling was specified so don't restack at
1124 all, it won't make sense no matter what we do */
1129 if (!config_focus_under_mouse)
1130 ignore_start = event_start_ignore_all_enters();
1131 stacking_restack_request(client, sibling,
1132 e->xconfigurerequest.detail);
1133 if (!config_focus_under_mouse)
1134 event_end_ignore_all_enters(ignore_start);
1137 /* a stacking change moves the window without resizing */
1141 if ((e->xconfigurerequest.value_mask & CWX) ||
1142 (e->xconfigurerequest.value_mask & CWY) ||
1143 (e->xconfigurerequest.value_mask & CWWidth) ||
1144 (e->xconfigurerequest.value_mask & CWHeight))
1146 /* don't allow clients to move shaded windows (fvwm does this)
1148 if (e->xconfigurerequest.value_mask & CWX) {
1149 if (!client->shaded)
1150 x = e->xconfigurerequest.x;
1153 if (e->xconfigurerequest.value_mask & CWY) {
1154 if (!client->shaded)
1155 y = e->xconfigurerequest.y;
1159 if (e->xconfigurerequest.value_mask & CWWidth) {
1160 w = e->xconfigurerequest.width;
1163 if (e->xconfigurerequest.value_mask & CWHeight) {
1164 h = e->xconfigurerequest.height;
1169 ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
1170 "move %d resize %d\n",
1171 e->xconfigurerequest.value_mask & CWX, x,
1172 e->xconfigurerequest.value_mask & CWY, y,
1173 e->xconfigurerequest.value_mask & CWWidth, w,
1174 e->xconfigurerequest.value_mask & CWHeight, h,
1177 /* check for broken apps moving to their root position
1179 XXX remove this some day...that would be nice. right now all
1180 kde apps do this when they try activate themselves on another
1181 desktop. eg. open amarok window on desktop 1, switch to desktop
1182 2, click amarok tray icon. it will move by its decoration size.
1184 if (x != client->area.x &&
1185 x == (client->frame->area.x + client->frame->size.left -
1186 (gint)client->border_width) &&
1187 y != client->area.y &&
1188 y == (client->frame->area.y + client->frame->size.top -
1189 (gint)client->border_width) &&
1190 w == client->area.width &&
1191 h == client->area.height)
1193 ob_debug_type(OB_DEBUG_APP_BUGS,
1194 "Application %s is trying to move via "
1195 "ConfigureRequest to it's root window position "
1196 "but it is not using StaticGravity\n",
1202 /* they still requested a move, so don't change whether a
1203 notify is sent or not */
1209 client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1211 /* if x was not given, then use gravity to figure out the new
1212 x. the reference point should not be moved */
1213 if ((e->xconfigurerequest.value_mask & CWWidth &&
1214 !(e->xconfigurerequest.value_mask & CWX)))
1215 client_gravity_resize_w(client, &x, client->area.width, w);
1217 if ((e->xconfigurerequest.value_mask & CWHeight &&
1218 !(e->xconfigurerequest.value_mask & CWY)))
1219 client_gravity_resize_h(client, &y, client->area.height,h);
1221 client_find_onscreen(client, &x, &y, w, h, FALSE);
1223 ob_debug("Granting ConfigureRequest x %d y %d w %d h %d\n",
1225 client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
1230 if (client->ignore_unmaps) {
1231 client->ignore_unmaps--;
1234 ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1235 "ignores left %d\n",
1236 client->window, e->xunmap.event, e->xunmap.from_configure,
1237 client->ignore_unmaps);
1238 client_unmanage(client);
1241 ob_debug("DestroyNotify for window 0x%x\n", client->window);
1242 client_unmanage(client);
1244 case ReparentNotify:
1245 /* this is when the client is first taken captive in the frame */
1246 if (e->xreparent.parent == client->frame->window) break;
1249 This event is quite rare and is usually handled in unmapHandler.
1250 However, if the window is unmapped when the reparent event occurs,
1251 the window manager never sees it because an unmap event is not sent
1252 to an already unmapped window.
1255 /* we don't want the reparent event, put it back on the stack for the
1256 X server to deal with after we unmanage the window */
1257 XPutBackEvent(ob_display, e);
1259 ob_debug("ReparentNotify for window 0x%x\n", client->window);
1260 client_unmanage(client);
1263 ob_debug("MapRequest for 0x%lx\n", client->window);
1264 if (!client->iconic) break; /* this normally doesn't happen, but if it
1265 does, we don't want it!
1266 it can happen now when the window is on
1267 another desktop, but we still don't
1269 client_activate(client, FALSE, FALSE, TRUE, TRUE, TRUE);
1272 /* validate cuz we query stuff off the client here */
1273 if (!client_validate(client)) break;
1275 if (e->xclient.format != 32) return;
1277 msgtype = e->xclient.message_type;
1278 if (msgtype == prop_atoms.wm_change_state) {
1279 compress_client_message_event(e, &ce, client->window, msgtype);
1280 client_set_wm_state(client, e->xclient.data.l[0]);
1281 } else if (msgtype == prop_atoms.net_wm_desktop) {
1282 compress_client_message_event(e, &ce, client->window, msgtype);
1283 if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1284 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1285 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1287 } else if (msgtype == prop_atoms.net_wm_state) {
1288 gulong ignore_start;
1290 /* can't compress these */
1291 ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1292 (e->xclient.data.l[0] == 0 ? "Remove" :
1293 e->xclient.data.l[0] == 1 ? "Add" :
1294 e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1295 e->xclient.data.l[1], e->xclient.data.l[2],
1298 /* ignore enter events caused by these like ob actions do */
1299 if (!config_focus_under_mouse)
1300 ignore_start = event_start_ignore_all_enters();
1301 client_set_state(client, e->xclient.data.l[0],
1302 e->xclient.data.l[1], e->xclient.data.l[2]);
1303 if (!config_focus_under_mouse)
1304 event_end_ignore_all_enters(ignore_start);
1305 } else if (msgtype == prop_atoms.net_close_window) {
1306 ob_debug("net_close_window for 0x%lx\n", client->window);
1307 client_close(client);
1308 } else if (msgtype == prop_atoms.net_active_window) {
1309 ob_debug("net_active_window for 0x%lx source=%s\n",
1311 (e->xclient.data.l[0] == 0 ? "unknown" :
1312 (e->xclient.data.l[0] == 1 ? "application" :
1313 (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1314 /* XXX make use of data.l[2] !? */
1315 if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
1316 event_curtime = e->xclient.data.l[1];
1317 if (e->xclient.data.l[1] == 0)
1318 ob_debug_type(OB_DEBUG_APP_BUGS,
1319 "_NET_ACTIVE_WINDOW message for window %s is"
1320 " missing a timestamp\n", client->title);
1322 ob_debug_type(OB_DEBUG_APP_BUGS,
1323 "_NET_ACTIVE_WINDOW message for window %s is "
1324 "missing source indication\n", client->title);
1325 client_activate(client, FALSE, FALSE, TRUE, TRUE,
1326 (e->xclient.data.l[0] == 0 ||
1327 e->xclient.data.l[0] == 2));
1328 } else if (msgtype == prop_atoms.net_wm_moveresize) {
1329 ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1330 client->window, e->xclient.data.l[2]);
1331 if ((Atom)e->xclient.data.l[2] ==
1332 prop_atoms.net_wm_moveresize_size_topleft ||
1333 (Atom)e->xclient.data.l[2] ==
1334 prop_atoms.net_wm_moveresize_size_top ||
1335 (Atom)e->xclient.data.l[2] ==
1336 prop_atoms.net_wm_moveresize_size_topright ||
1337 (Atom)e->xclient.data.l[2] ==
1338 prop_atoms.net_wm_moveresize_size_right ||
1339 (Atom)e->xclient.data.l[2] ==
1340 prop_atoms.net_wm_moveresize_size_right ||
1341 (Atom)e->xclient.data.l[2] ==
1342 prop_atoms.net_wm_moveresize_size_bottomright ||
1343 (Atom)e->xclient.data.l[2] ==
1344 prop_atoms.net_wm_moveresize_size_bottom ||
1345 (Atom)e->xclient.data.l[2] ==
1346 prop_atoms.net_wm_moveresize_size_bottomleft ||
1347 (Atom)e->xclient.data.l[2] ==
1348 prop_atoms.net_wm_moveresize_size_left ||
1349 (Atom)e->xclient.data.l[2] ==
1350 prop_atoms.net_wm_moveresize_move ||
1351 (Atom)e->xclient.data.l[2] ==
1352 prop_atoms.net_wm_moveresize_size_keyboard ||
1353 (Atom)e->xclient.data.l[2] ==
1354 prop_atoms.net_wm_moveresize_move_keyboard) {
1356 moveresize_start(client, e->xclient.data.l[0],
1357 e->xclient.data.l[1], e->xclient.data.l[3],
1358 e->xclient.data.l[2]);
1360 else if ((Atom)e->xclient.data.l[2] ==
1361 prop_atoms.net_wm_moveresize_cancel)
1362 moveresize_end(TRUE);
1363 } else if (msgtype == prop_atoms.net_moveresize_window) {
1364 gint ograv, x, y, w, h;
1366 ograv = client->gravity;
1368 if (e->xclient.data.l[0] & 0xff)
1369 client->gravity = e->xclient.data.l[0] & 0xff;
1371 if (e->xclient.data.l[0] & 1 << 8)
1372 x = e->xclient.data.l[1];
1375 if (e->xclient.data.l[0] & 1 << 9)
1376 y = e->xclient.data.l[2];
1380 if (e->xclient.data.l[0] & 1 << 10) {
1381 w = e->xclient.data.l[3];
1383 /* if x was not given, then use gravity to figure out the new
1384 x. the reference point should not be moved */
1385 if (!(e->xclient.data.l[0] & 1 << 8))
1386 client_gravity_resize_w(client, &x, client->area.width, w);
1389 w = client->area.width;
1391 if (e->xclient.data.l[0] & 1 << 11) {
1392 h = e->xclient.data.l[4];
1395 if (!(e->xclient.data.l[0] & 1 << 9))
1396 client_gravity_resize_h(client, &y, client->area.height,h);
1399 h = client->area.height;
1401 ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)\n",
1402 e->xclient.data.l[0] & 1 << 8, x,
1403 e->xclient.data.l[0] & 1 << 9, y,
1406 client_find_onscreen(client, &x, &y, w, h, FALSE);
1408 client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1410 client->gravity = ograv;
1411 } else if (msgtype == prop_atoms.net_restack_window) {
1412 if (e->xclient.data.l[0] != 2) {
1413 ob_debug_type(OB_DEBUG_APP_BUGS,
1414 "_NET_RESTACK_WINDOW sent for window %s with "
1415 "invalid source indication %ld\n",
1416 client->title, e->xclient.data.l[0]);
1418 ObClient *sibling = NULL;
1419 if (e->xclient.data.l[1]) {
1420 ObWindow *win = g_hash_table_lookup
1421 (window_map, &e->xclient.data.l[1]);
1422 if (WINDOW_IS_CLIENT(win) &&
1423 WINDOW_AS_CLIENT(win) != client)
1425 sibling = WINDOW_AS_CLIENT(win);
1427 if (sibling == NULL)
1428 ob_debug_type(OB_DEBUG_APP_BUGS,
1429 "_NET_RESTACK_WINDOW sent for window %s "
1430 "with invalid sibling 0x%x\n",
1431 client->title, e->xclient.data.l[1]);
1433 if (e->xclient.data.l[2] == Below ||
1434 e->xclient.data.l[2] == BottomIf ||
1435 e->xclient.data.l[2] == Above ||
1436 e->xclient.data.l[2] == TopIf ||
1437 e->xclient.data.l[2] == Opposite)
1439 gulong ignore_start;
1441 if (!config_focus_under_mouse)
1442 ignore_start = event_start_ignore_all_enters();
1443 /* just raise, don't activate */
1444 stacking_restack_request(client, sibling,
1445 e->xclient.data.l[2]);
1446 if (!config_focus_under_mouse)
1447 event_end_ignore_all_enters(ignore_start);
1449 /* send a synthetic ConfigureNotify, cuz this is supposed
1450 to be like a ConfigureRequest. */
1451 client_reconfigure(client, TRUE);
1453 ob_debug_type(OB_DEBUG_APP_BUGS,
1454 "_NET_RESTACK_WINDOW sent for window %s "
1455 "with invalid detail %d\n",
1456 client->title, e->xclient.data.l[2]);
1460 case PropertyNotify:
1461 /* validate cuz we query stuff off the client here */
1462 if (!client_validate(client)) break;
1464 /* compress changes to a single property into a single change */
1465 while (XCheckTypedWindowEvent(ob_display, client->window,
1469 /* XXX: it would be nice to compress ALL changes to a property,
1470 not just changes in a row without other props between. */
1472 a = ce.xproperty.atom;
1473 b = e->xproperty.atom;
1477 if ((a == prop_atoms.net_wm_name ||
1478 a == prop_atoms.wm_name ||
1479 a == prop_atoms.net_wm_icon_name ||
1480 a == prop_atoms.wm_icon_name)
1482 (b == prop_atoms.net_wm_name ||
1483 b == prop_atoms.wm_name ||
1484 b == prop_atoms.net_wm_icon_name ||
1485 b == prop_atoms.wm_icon_name)) {
1488 if (a == prop_atoms.net_wm_icon &&
1489 b == prop_atoms.net_wm_icon)
1492 XPutBackEvent(ob_display, &ce);
1496 msgtype = e->xproperty.atom;
1497 if (msgtype == XA_WM_NORMAL_HINTS) {
1498 ob_debug("Update NORMAL hints\n");
1499 client_update_normal_hints(client);
1500 /* normal hints can make a window non-resizable */
1501 client_setup_decor_and_functions(client, FALSE);
1503 /* make sure the client's sizes are within its bounds, but only
1504 reconfigure the window if it needs to. emacs will update its
1505 normal hints every time it receives a conigurenotify */
1506 client_reconfigure(client, FALSE);
1507 } else if (msgtype == prop_atoms.motif_wm_hints) {
1508 client_get_mwm_hints(client);
1509 /* This can override some mwm hints */
1510 client_get_type_and_transientness(client);
1512 /* Apply the changes to the window */
1513 client_setup_decor_and_functions(client, TRUE);
1514 } else if (msgtype == XA_WM_HINTS) {
1515 client_update_wmhints(client);
1516 } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1517 client_update_transient_for(client);
1518 client_get_type_and_transientness(client);
1519 /* type may have changed, so update the layer */
1520 client_calc_layer(client);
1521 client_setup_decor_and_functions(client, TRUE);
1522 } else if (msgtype == prop_atoms.net_wm_name ||
1523 msgtype == prop_atoms.wm_name ||
1524 msgtype == prop_atoms.net_wm_icon_name ||
1525 msgtype == prop_atoms.wm_icon_name) {
1526 client_update_title(client);
1527 } else if (msgtype == prop_atoms.wm_protocols) {
1528 client_update_protocols(client);
1529 client_setup_decor_and_functions(client, TRUE);
1531 else if (msgtype == prop_atoms.net_wm_strut ||
1532 msgtype == prop_atoms.net_wm_strut_partial) {
1533 client_update_strut(client);
1535 else if (msgtype == prop_atoms.net_wm_icon) {
1536 client_update_icons(client);
1538 else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1539 client_update_icon_geometry(client);
1541 else if (msgtype == prop_atoms.net_wm_user_time) {
1543 if (client == focus_client &&
1544 PROP_GET32(client->window, net_wm_user_time, cardinal, &t) &&
1545 t && !event_time_after(t, e->xproperty.time) &&
1546 (!event_last_user_time ||
1547 event_time_after(t, event_last_user_time)))
1549 event_last_user_time = t;
1553 else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1554 client_update_sync_request_counter(client);
1558 case ColormapNotify:
1559 client_update_colormap(client, e->xcolormap.colormap);
1566 if (extensions_shape && e->type == extensions_shape_event_basep) {
1567 switch (((XShapeEvent*)e)->kind) {
1570 client->shaped = ((XShapeEvent*)e)->shaped;
1571 kind = ShapeBounding;
1574 client->shaped_input = ((XShapeEvent*)e)->shaped;
1578 frame_adjust_shape_kind(client->frame, kind);
1585 static void event_handle_dock(ObDock *s, XEvent *e)
1589 if (e->xbutton.button == 1)
1590 stacking_raise(DOCK_AS_WINDOW(s));
1591 else if (e->xbutton.button == 2)
1592 stacking_lower(DOCK_AS_WINDOW(s));
1598 /* don't hide when moving into a dock app */
1599 if (e->xcrossing.detail != NotifyInferior)
1605 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1609 dock_app_drag(app, &e->xmotion);
1612 if (app->ignore_unmaps) {
1613 app->ignore_unmaps--;
1616 dock_remove(app, TRUE);
1619 case ReparentNotify:
1620 dock_remove(app, FALSE);
1622 case ConfigureNotify:
1623 dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1628 static ObMenuFrame* find_active_menu(void)
1631 ObMenuFrame *ret = NULL;
1633 for (it = menu_frame_visible; it; it = g_list_next(it)) {
1642 static ObMenuFrame* find_active_or_last_menu(void)
1644 ObMenuFrame *ret = NULL;
1646 ret = find_active_menu();
1647 if (!ret && menu_frame_visible)
1648 ret = menu_frame_visible->data;
1652 static gboolean event_handle_prompt(ObPrompt *p, XEvent *e)
1658 return prompt_mouse_event(p, e);
1661 return prompt_key_event(p, e);
1667 static gboolean event_handle_menu_keyboard(XEvent *ev)
1669 guint keycode, state;
1672 gboolean ret = FALSE;
1674 keycode = ev->xkey.keycode;
1675 state = ev->xkey.state;
1676 unikey = translate_unichar(keycode);
1678 frame = find_active_or_last_menu();
1680 g_assert_not_reached(); /* there is no active menu */
1682 /* Allow control while going thru the menu */
1683 else if (ev->type == KeyPress && (state & ~ControlMask) == 0) {
1684 frame->got_press = TRUE;
1686 if (ob_keycode_match(keycode, OB_KEY_ESCAPE)) {
1687 menu_frame_hide_all();
1691 else if (ob_keycode_match(keycode, OB_KEY_LEFT)) {
1692 /* Left goes to the parent menu */
1693 if (frame->parent) {
1694 /* remove focus from the child */
1695 menu_frame_select(frame, NULL, TRUE);
1696 /* and put it in the parent */
1697 menu_frame_select(frame->parent, frame->parent->selected,
1703 else if (ob_keycode_match(keycode, OB_KEY_RIGHT)) {
1704 /* Right goes to the selected submenu */
1705 if (frame->selected->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
1706 /* make sure it is visible */
1707 menu_frame_select(frame, frame->selected, TRUE);
1708 menu_frame_select_next(frame->child);
1713 else if (ob_keycode_match(keycode, OB_KEY_UP)) {
1714 menu_frame_select_previous(frame);
1718 else if (ob_keycode_match(keycode, OB_KEY_DOWN)) {
1719 menu_frame_select_next(frame);
1723 else if (ob_keycode_match(keycode, OB_KEY_HOME)) {
1724 menu_frame_select_first(frame);
1728 else if (ob_keycode_match(keycode, OB_KEY_END)) {
1729 menu_frame_select_last(frame);
1734 /* Use KeyRelease events for running things so that the key release doesn't
1735 get sent to the focused application.
1737 Allow ControlMask only, and don't bother if the menu is empty */
1738 else if (ev->type == KeyRelease && (state & ~ControlMask) == 0 &&
1739 frame->entries && frame->got_press)
1741 if (ob_keycode_match(keycode, OB_KEY_RETURN)) {
1742 /* Enter runs the active item or goes into the submenu.
1743 Control-Enter runs it without closing the menu. */
1745 menu_frame_select_next(frame->child);
1746 else if (frame->selected)
1747 menu_entry_frame_execute(frame->selected, state);
1752 /* keyboard accelerator shortcuts. (if it was a valid key) */
1753 else if (unikey != 0) {
1756 ObMenuEntryFrame *found = NULL;
1757 guint num_found = 0;
1759 /* start after the selected one */
1760 start = frame->entries;
1761 if (frame->selected) {
1762 for (it = start; frame->selected != it->data;
1763 it = g_list_next(it))
1764 g_assert(it != NULL); /* nothing was selected? */
1765 /* next with wraparound */
1766 start = g_list_next(it);
1767 if (start == NULL) start = frame->entries;
1772 ObMenuEntryFrame *e = it->data;
1773 gunichar entrykey = 0;
1775 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1776 entrykey = e->entry->data.normal.shortcut;
1777 else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1778 entrykey = e->entry->data.submenu.submenu->shortcut;
1780 if (unikey == entrykey) {
1781 if (found == NULL) found = e;
1785 /* next with wraparound */
1786 it = g_list_next(it);
1787 if (it == NULL) it = frame->entries;
1788 } while (it != start);
1791 if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1794 menu_frame_select(frame, found, TRUE);
1795 usleep(50000); /* highlight the item for a short bit so the
1796 user can see what happened */
1797 menu_entry_frame_execute(found, state);
1799 menu_frame_select(frame, found, TRUE);
1801 menu_frame_select_next(frame->child);
1812 static Bool event_look_for_menu_enter(Display *d, XEvent *ev, XPointer arg)
1814 ObMenuFrame *f = (ObMenuFrame*)arg;
1815 ObMenuEntryFrame *e;
1816 return ev->type == EnterNotify &&
1817 (e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1818 !e->ignore_enters && e->frame == f;
1821 static gboolean event_handle_menu(XEvent *ev)
1824 ObMenuEntryFrame *e;
1825 gboolean ret = TRUE;
1829 if (menu_hide_delay_reached() &&
1830 (ev->xbutton.button < 4 || ev->xbutton.button > 5))
1832 if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1833 ev->xbutton.y_root)))
1835 menu_frame_select(e->frame, e, TRUE);
1836 menu_entry_frame_execute(e, ev->xbutton.state);
1839 menu_frame_hide_all();
1843 if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1844 if (e->ignore_enters)
1846 else if (!(f = find_active_menu()) ||
1848 f->parent == e->frame ||
1849 f->child == e->frame)
1850 menu_frame_select(e->frame, e, FALSE);
1854 /*ignore leaves when we're already in the window */
1855 if (ev->xcrossing.detail == NotifyInferior)
1858 if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)))
1862 /* check if an EnterNotify event is coming, and if not, then select
1863 nothing in the menu */
1864 if (XCheckIfEvent(ob_display, &ce, event_look_for_menu_enter,
1865 (XPointer)e->frame))
1866 XPutBackEvent(ob_display, &ce);
1868 menu_frame_select(e->frame, NULL, FALSE);
1872 if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1873 ev->xmotion.y_root)))
1874 if (!(f = find_active_menu()) ||
1876 f->parent == e->frame ||
1877 f->child == e->frame)
1878 menu_frame_select(e->frame, e, FALSE);
1882 ret = event_handle_menu_keyboard(ev);
1888 static void event_handle_user_input(ObClient *client, XEvent *e)
1890 g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1891 e->type == MotionNotify || e->type == KeyPress ||
1892 e->type == KeyRelease);
1894 if (menu_frame_visible) {
1895 if (event_handle_menu(e))
1896 /* don't use the event if the menu used it, but if the menu
1897 didn't use it and it's a keypress that is bound, it will
1898 close the menu and be used */
1902 /* if the keyboard interactive action uses the event then dont
1903 use it for bindings. likewise is moveresize uses the event. */
1904 if (!actions_interactive_input_event(e) && !moveresize_event(e)) {
1905 if (moveresize_in_progress)
1906 /* make further actions work on the client being
1908 client = moveresize_client;
1910 if (e->type == ButtonPress ||
1911 e->type == ButtonRelease ||
1912 e->type == MotionNotify)
1914 /* the frame may not be "visible" but they can still click on it
1915 in the case where it is animating before disappearing */
1916 if (!client || !frame_iconify_animating(client->frame))
1917 mouse_event(client, e);
1919 keyboard_event((focus_cycle_target ? focus_cycle_target :
1920 (client ? client : focus_client)), e);
1924 static void focus_delay_dest(gpointer data)
1929 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1931 const ObFocusDelayData *f1 = d1;
1932 return f1->client == d2;
1935 static gboolean focus_delay_func(gpointer data)
1937 ObFocusDelayData *d = data;
1938 Time old = event_curtime;
1940 event_curtime = d->time;
1941 event_curserial = d->serial;
1942 if (client_focus(d->client) && config_focus_raise)
1943 stacking_raise(CLIENT_AS_WINDOW(d->client));
1944 event_curtime = old;
1945 return FALSE; /* no repeat */
1948 static void focus_delay_client_dest(ObClient *client, gpointer data)
1950 ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1954 void event_halt_focus_delay(void)
1956 /* ignore all enter events up till the event which caused this to occur */
1957 if (event_curserial) event_ignore_enter_range(1, event_curserial);
1958 ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1961 gulong event_start_ignore_all_enters(void)
1963 return NextRequest(ob_display);
1966 static void event_ignore_enter_range(gulong start, gulong end)
1970 g_assert(start != 0);
1973 r = g_new(ObSerialRange, 1);
1976 ignore_serials = g_slist_prepend(ignore_serials, r);
1978 ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu\n",
1981 /* increment the serial so we don't ignore events we weren't meant to */
1982 PROP_ERASE(screen_support_win, motif_wm_hints);
1985 void event_end_ignore_all_enters(gulong start)
1987 /* Use (NextRequest-1) so that we ignore up to the current serial only.
1988 Inside event_ignore_enter_range, we increment the serial by one, but if
1989 we ignore that serial too, then any enter events generated by mouse
1990 movement will be ignored until we create some further network traffic.
1991 Instead ignore up to NextRequest-1, then when we increment the serial,
1992 we will be *past* the range of ignored serials */
1993 event_ignore_enter_range(start, NextRequest(ob_display)-1);
1996 static gboolean is_enter_focus_event_ignored(gulong serial)
2000 for (it = ignore_serials; it; it = next) {
2001 ObSerialRange *r = it->data;
2003 next = g_slist_next(it);
2005 if ((glong)(serial - r->end) > 0) {
2007 ignore_serials = g_slist_delete_link(ignore_serials, it);
2010 else if ((glong)(serial - r->start) >= 0)
2016 void event_cancel_all_key_grabs(void)
2018 if (actions_interactive_act_running()) {
2019 actions_interactive_cancel_act();
2020 ob_debug("KILLED interactive action\n");
2022 else if (menu_frame_visible) {
2023 menu_frame_hide_all();
2024 ob_debug("KILLED open menus\n");
2026 else if (moveresize_in_progress) {
2027 moveresize_end(TRUE);
2028 ob_debug("KILLED interactive moveresize\n");
2030 else if (grab_on_keyboard()) {
2032 ob_debug("KILLED active grab on keyboard\n");
2035 ungrab_passive_key();
2037 XSync(ob_display, FALSE);
2040 gboolean event_time_after(guint32 t1, guint32 t2)
2042 g_assert(t1 != CurrentTime);
2043 g_assert(t2 != CurrentTime);
2046 Timestamp values wrap around (after about 49.7 days). The server, given
2047 its current time is represented by timestamp T, always interprets
2048 timestamps from clients by treating half of the timestamp space as being
2049 later in time than T.
2050 - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
2053 /* TIME_HALF is not half of the number space of a Time type variable.
2054 * Rather, it is half the number space of a timestamp value, which is
2055 * always 32 bits. */
2056 #define TIME_HALF (guint32)(1 << 31)
2058 if (t2 >= TIME_HALF)
2059 /* t2 is in the second half so t1 might wrap around and be smaller than
2061 return t1 >= t2 || t1 < (t2 + TIME_HALF);
2063 /* t2 is in the first half so t1 has to come after it */
2064 return t1 >= t2 && t1 < (t2 + TIME_HALF);
2067 Time event_get_server_time(void)
2069 /* Generate a timestamp */
2072 XChangeProperty(ob_display, screen_support_win,
2073 prop_atoms.wm_class, prop_atoms.string,
2074 8, PropModeAppend, NULL, 0);
2075 XWindowEvent(ob_display, screen_support_win, PropertyChangeMask, &event);
2076 return event.xproperty.time;