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.
32 #include "menuframe.h"
38 #include "framerender.h"
40 #include "moveresize.h"
43 #include "extensions.h"
44 #include "translate.h"
47 #include <X11/Xatom.h>
50 #ifdef HAVE_SYS_SELECT_H
51 # include <sys/select.h>
57 # include <unistd.h> /* for usleep() */
60 # include <X11/XKBlib.h>
64 #include <X11/ICE/ICElib.h>
78 static void event_process(const XEvent
*e
, gpointer data
);
79 static void event_handle_root(XEvent
*e
);
80 static gboolean
event_handle_menu_keyboard(XEvent
*e
);
81 static gboolean
event_handle_menu(XEvent
*e
);
82 static void event_handle_dock(ObDock
*s
, XEvent
*e
);
83 static void event_handle_dockapp(ObDockApp
*app
, XEvent
*e
);
84 static void event_handle_client(ObClient
*c
, XEvent
*e
);
85 static void event_handle_user_time_window_clients(GSList
*l
, XEvent
*e
);
86 static void event_handle_user_input(ObClient
*client
, XEvent
*e
);
88 static void focus_delay_dest(gpointer data
);
89 static gboolean
focus_delay_cmp(gconstpointer d1
, gconstpointer d2
);
90 static gboolean
focus_delay_func(gpointer data
);
91 static void focus_delay_client_dest(ObClient
*client
, gpointer data
);
93 static gboolean
menu_hide_delay_func(gpointer data
);
95 /* The time for the current event being processed */
96 Time event_curtime
= CurrentTime
;
98 static guint ignore_enter_focus
= 0;
99 static gboolean menu_can_hide
;
100 static gboolean focus_left_screen
= FALSE
;
101 /*! This variable is used for focus fallback. If we fallback to a window, we
102 set this to the window. And when focus goes somewhere after that, it will
103 be set to NULL. If between falling back to that window and something
104 getting focused, the window gets unmanaged, then if there are no incoming
105 FocusIn events, we fallback again because focus has just gotten itself lost.
107 static ObClient
*focus_tried
= 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 ob_main_loop_fd_add(ob_main_loop
, fd
, ice_handler
, conn
, NULL
);
125 ob_main_loop_fd_remove(ob_main_loop
, fd
);
131 void event_startup(gboolean reconfig
)
133 if (reconfig
) return;
135 ob_main_loop_x_add(ob_main_loop
, event_process
, NULL
, NULL
);
138 IceAddConnectionWatch(ice_watch
, NULL
);
141 client_add_destructor(focus_delay_client_dest
, NULL
);
144 void event_shutdown(gboolean reconfig
)
146 if (reconfig
) return;
149 IceRemoveConnectionWatch(ice_watch
, NULL
);
152 client_remove_destructor(focus_delay_client_dest
);
155 static Window
event_get_window(XEvent
*e
)
162 window
= RootWindow(ob_display
, ob_screen
);
165 window
= e
->xmap
.window
;
168 window
= e
->xunmap
.window
;
171 window
= e
->xdestroywindow
.window
;
173 case ConfigureRequest
:
174 window
= e
->xconfigurerequest
.window
;
176 case ConfigureNotify
:
177 window
= e
->xconfigure
.window
;
181 if (extensions_xkb
&& e
->type
== extensions_xkb_event_basep
) {
182 switch (((XkbAnyEvent
*)e
)->xkb_type
) {
184 window
= ((XkbBellNotifyEvent
*)e
)->window
;
191 if (extensions_sync
&&
192 e
->type
== extensions_sync_event_basep
+ XSyncAlarmNotify
)
197 window
= e
->xany
.window
;
202 static void event_set_curtime(XEvent
*e
)
204 Time t
= CurrentTime
;
206 /* grab the lasttime and hack up the state */
222 t
= e
->xproperty
.time
;
226 t
= e
->xcrossing
.time
;
230 if (extensions_sync
&&
231 e
->type
== extensions_sync_event_basep
+ XSyncAlarmNotify
)
233 t
= ((XSyncAlarmNotifyEvent
*)e
)->time
;
236 /* if more event types are anticipated, get their timestamp
244 static void event_hack_mods(XEvent
*e
)
247 XkbStateRec xkb_state
;
253 e
->xbutton
.state
= modkeys_only_modifier_masks(e
->xbutton
.state
);
256 e
->xkey
.state
= modkeys_only_modifier_masks(e
->xkey
.state
);
259 e
->xkey
.state
= modkeys_only_modifier_masks(e
->xkey
.state
);
261 if (XkbGetState(ob_display
, XkbUseCoreKbd
, &xkb_state
) == Success
) {
262 e
->xkey
.state
= xkb_state
.compat_state
;
266 /* remove from the state the mask of the modifier key being released,
267 if it is a modifier key being released that is */
268 e
->xkey
.state
&= ~modkeys_keycode_to_mask(e
->xkey
.keycode
);
271 e
->xmotion
.state
= modkeys_only_modifier_masks(e
->xmotion
.state
);
272 /* compress events */
275 while (XCheckTypedWindowEvent(ob_display
, e
->xmotion
.window
,
277 e
->xmotion
.x_root
= ce
.xmotion
.x_root
;
278 e
->xmotion
.y_root
= ce
.xmotion
.y_root
;
285 static gboolean
wanted_focusevent(XEvent
*e
, gboolean in_client_only
)
287 gint mode
= e
->xfocus
.mode
;
288 gint detail
= e
->xfocus
.detail
;
289 Window win
= e
->xany
.window
;
291 if (e
->type
== FocusIn
) {
292 /* These are ones we never want.. */
294 /* This means focus was given by a keyboard/mouse grab. */
295 if (mode
== NotifyGrab
)
297 /* This means focus was given back from a keyboard/mouse grab. */
298 if (mode
== NotifyUngrab
)
301 /* These are the ones we want.. */
303 if (win
== RootWindow(ob_display
, ob_screen
)) {
304 /* If looking for a focus in on a client, then always return
305 FALSE for focus in's to the root window */
308 /* This means focus reverted off of a client */
309 else if (detail
== NotifyPointerRoot
||
310 detail
== NotifyDetailNone
||
311 detail
== NotifyInferior
)
317 /* This means focus moved from the root window to a client */
318 if (detail
== NotifyVirtual
)
320 /* This means focus moved from one client to another */
321 if (detail
== NotifyNonlinearVirtual
)
323 /* This means focus moved to the frame window */
324 if (detail
== NotifyInferior
&& !in_client_only
)
330 g_assert(e
->type
== FocusOut
);
332 /* These are ones we never want.. */
334 /* This means focus was taken by a keyboard/mouse grab. */
335 if (mode
== NotifyGrab
)
338 /* Focus left the root window revertedto state */
339 if (win
== RootWindow(ob_display
, ob_screen
))
342 /* These are the ones we want.. */
344 /* This means focus moved from a client to the root window */
345 if (detail
== NotifyVirtual
)
347 /* This means focus moved from one client to another */
348 if (detail
== NotifyNonlinearVirtual
)
350 /* This means focus had moved to our frame window and now moved off */
351 if (detail
== NotifyNonlinear
)
359 static Bool
look_for_focusin(Display
*d
, XEvent
*e
, XPointer arg
)
361 return e
->type
== FocusIn
&& wanted_focusevent(e
, FALSE
);
364 static Bool
look_for_focusin_client(Display
*d
, XEvent
*e
, XPointer arg
)
366 return e
->type
== FocusIn
&& wanted_focusevent(e
, TRUE
);
369 static void print_focusevent(XEvent
*e
)
371 gint mode
= e
->xfocus
.mode
;
372 gint detail
= e
->xfocus
.detail
;
373 Window win
= e
->xany
.window
;
374 const gchar
*modestr
, *detailstr
;
377 case NotifyNormal
: modestr
="NotifyNormal"; break;
378 case NotifyGrab
: modestr
="NotifyGrab"; break;
379 case NotifyUngrab
: modestr
="NotifyUngrab"; break;
380 case NotifyWhileGrabbed
: modestr
="NotifyWhileGrabbed"; break;
383 case NotifyAncestor
: detailstr
="NotifyAncestor"; break;
384 case NotifyVirtual
: detailstr
="NotifyVirtual"; break;
385 case NotifyInferior
: detailstr
="NotifyInferior"; break;
386 case NotifyNonlinear
: detailstr
="NotifyNonlinear"; break;
387 case NotifyNonlinearVirtual
: detailstr
="NotifyNonlinearVirtual"; break;
388 case NotifyPointer
: detailstr
="NotifyPointer"; break;
389 case NotifyPointerRoot
: detailstr
="NotifyPointerRoot"; break;
390 case NotifyDetailNone
: detailstr
="NotifyDetailNone"; break;
395 ob_debug_type(OB_DEBUG_FOCUS
, "Focus%s 0x%x mode=%s detail=%s\n",
396 (e
->xfocus
.type
== FocusIn
? "In" : "Out"),
402 static gboolean
event_ignore(XEvent
*e
, ObClient
*client
)
407 if (!wanted_focusevent(e
, FALSE
))
412 if (!wanted_focusevent(e
, FALSE
))
419 static void event_process(const XEvent
*ec
, gpointer data
)
422 ObClient
*client
= NULL
;
424 ObDockApp
*dockapp
= NULL
;
425 ObWindow
*obwin
= NULL
;
426 GSList
*timewinclients
= NULL
;
428 ObEventData
*ed
= data
;
430 /* make a copy we can mangle */
434 window
= event_get_window(e
);
435 if (e
->type
!= PropertyNotify
||
436 !(timewinclients
= propwin_get_clients(window
,
437 OB_PROPWIN_USER_TIME
)))
438 if ((obwin
= g_hash_table_lookup(window_map
, &window
))) {
439 switch (obwin
->type
) {
441 dock
= WINDOW_AS_DOCK(obwin
);
444 dockapp
= WINDOW_AS_DOCKAPP(obwin
);
447 client
= WINDOW_AS_CLIENT(obwin
);
450 case Window_Internal
:
451 /* not to be used for events */
452 g_assert_not_reached();
457 event_set_curtime(e
);
459 if (event_ignore(e
, client
)) {
466 /* deal with it in the kernel */
468 if (menu_frame_visible
&&
469 (e
->type
== EnterNotify
|| e
->type
== LeaveNotify
))
471 /* crossing events for menu */
472 event_handle_menu(e
);
473 } else if (e
->type
== FocusIn
) {
474 if (e
->xfocus
.detail
== NotifyPointerRoot
||
475 e
->xfocus
.detail
== NotifyDetailNone
||
476 e
->xfocus
.detail
== NotifyInferior
)
479 ob_debug_type(OB_DEBUG_FOCUS
,
480 "Focus went to pointer root/none or to our frame "
483 /* If another FocusIn is in the queue then don't fallback yet. This
484 fixes the fun case of:
485 window map -> send focusin
486 window unmap -> get focusout
487 window map -> send focusin
488 get first focus out -> fall back to something (new window
489 hasn't received focus yet, so something else) -> send focusin
490 which means the "something else" is the last thing to get a
491 focusin sent to it, so the new window doesn't end up with focus.
493 But if the other focus in is something like PointerRoot then we
494 still want to fall back.
496 if (XCheckIfEvent(ob_display
, &ce
, look_for_focusin_client
, NULL
)){
497 XPutBackEvent(ob_display
, &ce
);
498 ob_debug_type(OB_DEBUG_FOCUS
,
499 " but another FocusIn is coming\n");
501 /* Focus has been reverted to the root window, nothing, or to
504 FocusOut events come after UnmapNotify, so we don't need to
505 worry about focusing an invalid window
508 /* In this case we know focus is in our screen */
509 if (e
->xfocus
.detail
== NotifyInferior
)
510 focus_left_screen
= FALSE
;
512 if (!focus_left_screen
)
513 focus_tried
= focus_fallback(TRUE
);
515 } else if (client
&& client
!= focus_client
) {
516 focus_left_screen
= FALSE
;
517 frame_adjust_focus(client
->frame
, TRUE
);
518 focus_set_client(client
);
519 client_calc_layer(client
);
520 client_bring_helper_windows(client
);
522 focus_tried
= NULL
; /* focus isn't "trying" to go anywhere now */
524 } else if (e
->type
== FocusOut
) {
525 gboolean nomove
= FALSE
;
528 /* Look for the followup FocusIn */
529 if (!XCheckIfEvent(ob_display
, &ce
, look_for_focusin
, NULL
)) {
530 /* There is no FocusIn, this means focus went to a window that
531 is not being managed, or a window on another screen. */
535 xerror_set_ignore(TRUE
);
536 if (XGetInputFocus(ob_display
, &win
, &i
) != 0 &&
537 XGetGeometry(ob_display
, win
, &root
, &i
,&i
,&u
,&u
,&u
,&u
) != 0 &&
538 root
!= RootWindow(ob_display
, ob_screen
))
540 ob_debug_type(OB_DEBUG_FOCUS
,
541 "Focus went to another screen !\n");
542 focus_left_screen
= TRUE
;
545 ob_debug_type(OB_DEBUG_FOCUS
,
546 "Focus went to a black hole !\n");
547 xerror_set_ignore(FALSE
);
548 /* nothing is focused */
549 focus_set_client(NULL
);
550 } else if (ce
.xany
.window
== e
->xany
.window
) {
551 ob_debug_type(OB_DEBUG_FOCUS
, "Focus didn't go anywhere\n");
552 /* If focus didn't actually move anywhere, there is nothing to do*/
555 /* Focus did move, so process the FocusIn event */
556 ObEventData ed
= { .ignored
= FALSE
};
557 event_process(&ce
, &ed
);
559 /* The FocusIn was ignored, this means it was on a window
560 that isn't a client. */
561 ob_debug_type(OB_DEBUG_FOCUS
,
562 "Focus went to an unmanaged window 0x%x !\n",
564 focus_tried
= focus_fallback(TRUE
);
568 if (client
&& !nomove
) {
569 frame_adjust_focus(client
->frame
, FALSE
);
570 /* focus_set_client has already been called for sure */
571 client_calc_layer(client
);
573 } else if (timewinclients
)
574 event_handle_user_time_window_clients(timewinclients
, e
);
576 event_handle_client(client
, e
);
578 event_handle_dockapp(dockapp
, e
);
580 event_handle_dock(dock
, e
);
581 else if (window
== RootWindow(ob_display
, ob_screen
))
582 event_handle_root(e
);
583 else if (e
->type
== MapRequest
)
584 client_manage(window
);
585 else if (e
->type
== ClientMessage
) {
586 /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
587 windows that are not managed yet. */
588 if (e
->xclient
.message_type
== prop_atoms
.net_request_frame_extents
) {
589 /* Pretend to manage the client, getting information used to
590 determine its decorations */
591 ObClient
*c
= client_fake_manage(e
->xclient
.window
);
594 /* set the frame extents on the window */
595 vals
[0] = c
->frame
->size
.left
;
596 vals
[1] = c
->frame
->size
.right
;
597 vals
[2] = c
->frame
->size
.top
;
598 vals
[3] = c
->frame
->size
.bottom
;
599 PROP_SETA32(e
->xclient
.window
, net_frame_extents
,
602 /* Free the pretend client */
603 client_fake_unmanage(c
);
606 else if (e
->type
== ConfigureRequest
) {
607 /* unhandled configure requests must be used to configure the
611 xwc
.x
= e
->xconfigurerequest
.x
;
612 xwc
.y
= e
->xconfigurerequest
.y
;
613 xwc
.width
= e
->xconfigurerequest
.width
;
614 xwc
.height
= e
->xconfigurerequest
.height
;
615 xwc
.border_width
= e
->xconfigurerequest
.border_width
;
616 xwc
.sibling
= e
->xconfigurerequest
.above
;
617 xwc
.stack_mode
= e
->xconfigurerequest
.detail
;
619 /* we are not to be held responsible if someone sends us an
621 xerror_set_ignore(TRUE
);
622 XConfigureWindow(ob_display
, window
,
623 e
->xconfigurerequest
.value_mask
, &xwc
);
624 xerror_set_ignore(FALSE
);
627 else if (extensions_sync
&&
628 e
->type
== extensions_sync_event_basep
+ XSyncAlarmNotify
)
630 XSyncAlarmNotifyEvent
*se
= (XSyncAlarmNotifyEvent
*)e
;
631 if (se
->alarm
== moveresize_alarm
&& moveresize_in_progress
)
636 if (e
->type
== ButtonPress
|| e
->type
== ButtonRelease
||
637 e
->type
== MotionNotify
|| e
->type
== KeyPress
||
638 e
->type
== KeyRelease
)
640 event_handle_user_input(client
, e
);
643 /* if something happens and it's not from an XEvent, then we don't know
645 event_curtime
= CurrentTime
;
648 static void event_handle_root(XEvent
*e
)
654 ob_debug("Another WM has requested to replace us. Exiting.\n");
659 if (e
->xclient
.format
!= 32) break;
661 msgtype
= e
->xclient
.message_type
;
662 if (msgtype
== prop_atoms
.net_current_desktop
) {
663 guint d
= e
->xclient
.data
.l
[0];
664 if (d
< screen_num_desktops
) {
665 event_curtime
= e
->xclient
.data
.l
[1];
666 if (event_curtime
== 0)
667 ob_debug_type(OB_DEBUG_APP_BUGS
,
668 "_NET_CURRENT_DESKTOP message is missing "
670 screen_set_desktop(d
, TRUE
);
672 } else if (msgtype
== prop_atoms
.net_number_of_desktops
) {
673 guint d
= e
->xclient
.data
.l
[0];
675 screen_set_num_desktops(d
);
676 } else if (msgtype
== prop_atoms
.net_showing_desktop
) {
677 screen_show_desktop(e
->xclient
.data
.l
[0] != 0, NULL
);
678 } else if (msgtype
== prop_atoms
.openbox_control
) {
679 if (e
->xclient
.data
.l
[0] == 1)
681 else if (e
->xclient
.data
.l
[0] == 2)
686 if (e
->xproperty
.atom
== prop_atoms
.net_desktop_names
)
687 screen_update_desktop_names();
688 else if (e
->xproperty
.atom
== prop_atoms
.net_desktop_layout
)
689 screen_update_layout();
691 case ConfigureNotify
:
693 XRRUpdateConfiguration(e
);
702 void event_enter_client(ObClient
*client
)
704 g_assert(config_focus_follow
);
706 if (client_enter_focusable(client
) && client_can_focus(client
)) {
707 if (config_focus_delay
) {
708 ObFocusDelayData
*data
;
710 ob_main_loop_timeout_remove(ob_main_loop
, focus_delay_func
);
712 data
= g_new(ObFocusDelayData
, 1);
713 data
->client
= client
;
714 data
->time
= event_curtime
;
716 ob_main_loop_timeout_add(ob_main_loop
,
719 data
, focus_delay_cmp
, focus_delay_dest
);
721 ObFocusDelayData data
;
722 data
.client
= client
;
723 data
.time
= event_curtime
;
724 focus_delay_func(&data
);
729 static void event_handle_user_time_window_clients(GSList
*l
, XEvent
*e
)
731 g_assert(e
->type
== PropertyNotify
);
732 if (e
->xproperty
.atom
== prop_atoms
.net_wm_user_time
) {
733 for (; l
; l
= g_slist_next(l
))
734 client_update_user_time(l
->data
);
738 static void event_handle_client(ObClient
*client
, XEvent
*e
)
743 static gint px
= -1, py
= -1;
748 /* save where the press occured for the first button pressed */
750 pb
= e
->xbutton
.button
;
755 /* Wheel buttons don't draw because they are an instant click, so it
756 is a waste of resources to go drawing it.
757 if the user is doing an intereactive thing, or has a menu open then
758 the mouse is grabbed (possibly) and if we get these events we don't
759 want to deal with them
761 if (!(e
->xbutton
.button
== 4 || e
->xbutton
.button
== 5) &&
762 !keyboard_interactively_grabbed() &&
765 /* use where the press occured */
766 con
= frame_context(client
, e
->xbutton
.window
, px
, py
);
767 con
= mouse_button_frame_context(con
, e
->xbutton
.button
);
769 if (e
->type
== ButtonRelease
&& e
->xbutton
.button
== pb
)
770 pb
= 0, px
= py
= -1;
773 case OB_FRAME_CONTEXT_MAXIMIZE
:
774 client
->frame
->max_press
= (e
->type
== ButtonPress
);
775 framerender_frame(client
->frame
);
777 case OB_FRAME_CONTEXT_CLOSE
:
778 client
->frame
->close_press
= (e
->type
== ButtonPress
);
779 framerender_frame(client
->frame
);
781 case OB_FRAME_CONTEXT_ICONIFY
:
782 client
->frame
->iconify_press
= (e
->type
== ButtonPress
);
783 framerender_frame(client
->frame
);
785 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
786 client
->frame
->desk_press
= (e
->type
== ButtonPress
);
787 framerender_frame(client
->frame
);
789 case OB_FRAME_CONTEXT_SHADE
:
790 client
->frame
->shade_press
= (e
->type
== ButtonPress
);
791 framerender_frame(client
->frame
);
794 /* nothing changes with clicks for any other contexts */
800 con
= frame_context(client
, e
->xmotion
.window
,
801 e
->xmotion
.x
, e
->xmotion
.y
);
803 case OB_FRAME_CONTEXT_TITLEBAR
:
804 /* we've left the button area inside the titlebar */
805 if (client
->frame
->max_hover
|| client
->frame
->desk_hover
||
806 client
->frame
->shade_hover
|| client
->frame
->iconify_hover
||
807 client
->frame
->close_hover
)
809 client
->frame
->max_hover
= FALSE
;
810 client
->frame
->desk_hover
= FALSE
;
811 client
->frame
->shade_hover
= FALSE
;
812 client
->frame
->iconify_hover
= FALSE
;
813 client
->frame
->close_hover
= FALSE
;
814 frame_adjust_state(client
->frame
);
817 case OB_FRAME_CONTEXT_MAXIMIZE
:
818 if (!client
->frame
->max_hover
) {
819 client
->frame
->max_hover
= TRUE
;
820 frame_adjust_state(client
->frame
);
823 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
824 if (!client
->frame
->desk_hover
) {
825 client
->frame
->desk_hover
= TRUE
;
826 frame_adjust_state(client
->frame
);
829 case OB_FRAME_CONTEXT_SHADE
:
830 if (!client
->frame
->shade_hover
) {
831 client
->frame
->shade_hover
= TRUE
;
832 frame_adjust_state(client
->frame
);
835 case OB_FRAME_CONTEXT_ICONIFY
:
836 if (!client
->frame
->iconify_hover
) {
837 client
->frame
->iconify_hover
= TRUE
;
838 frame_adjust_state(client
->frame
);
841 case OB_FRAME_CONTEXT_CLOSE
:
842 if (!client
->frame
->close_hover
) {
843 client
->frame
->close_hover
= TRUE
;
844 frame_adjust_state(client
->frame
);
852 con
= frame_context(client
, e
->xcrossing
.window
,
853 e
->xcrossing
.x
, e
->xcrossing
.y
);
855 case OB_FRAME_CONTEXT_MAXIMIZE
:
856 client
->frame
->max_hover
= FALSE
;
857 frame_adjust_state(client
->frame
);
859 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
860 client
->frame
->desk_hover
= FALSE
;
861 frame_adjust_state(client
->frame
);
863 case OB_FRAME_CONTEXT_SHADE
:
864 client
->frame
->shade_hover
= FALSE
;
865 frame_adjust_state(client
->frame
);
867 case OB_FRAME_CONTEXT_ICONIFY
:
868 client
->frame
->iconify_hover
= FALSE
;
869 frame_adjust_state(client
->frame
);
871 case OB_FRAME_CONTEXT_CLOSE
:
872 client
->frame
->close_hover
= FALSE
;
873 frame_adjust_state(client
->frame
);
875 case OB_FRAME_CONTEXT_FRAME
:
876 /* When the mouse leaves an animating window, don't use the
877 corresponding enter events. Pretend like the animating window
878 doesn't even exist..! */
879 if (frame_iconify_animating(client
->frame
))
880 event_ignore_queued_enters();
882 ob_debug_type(OB_DEBUG_FOCUS
,
883 "%sNotify mode %d detail %d on %lx\n",
884 (e
->type
== EnterNotify
? "Enter" : "Leave"),
886 e
->xcrossing
.detail
, (client
?client
->window
:0));
887 if (keyboard_interactively_grabbed())
889 if (config_focus_follow
&& config_focus_delay
&&
890 /* leave inferior events can happen when the mouse goes onto
891 the window's border and then into the window before the
893 e
->xcrossing
.detail
!= NotifyInferior
)
895 ob_main_loop_timeout_remove_data(ob_main_loop
,
906 gboolean nofocus
= FALSE
;
908 if (ignore_enter_focus
) {
909 ignore_enter_focus
--;
913 con
= frame_context(client
, e
->xcrossing
.window
,
914 e
->xcrossing
.x
, e
->xcrossing
.y
);
916 case OB_FRAME_CONTEXT_MAXIMIZE
:
917 client
->frame
->max_hover
= TRUE
;
918 frame_adjust_state(client
->frame
);
920 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
921 client
->frame
->desk_hover
= TRUE
;
922 frame_adjust_state(client
->frame
);
924 case OB_FRAME_CONTEXT_SHADE
:
925 client
->frame
->shade_hover
= TRUE
;
926 frame_adjust_state(client
->frame
);
928 case OB_FRAME_CONTEXT_ICONIFY
:
929 client
->frame
->iconify_hover
= TRUE
;
930 frame_adjust_state(client
->frame
);
932 case OB_FRAME_CONTEXT_CLOSE
:
933 client
->frame
->close_hover
= TRUE
;
934 frame_adjust_state(client
->frame
);
936 case OB_FRAME_CONTEXT_FRAME
:
937 if (keyboard_interactively_grabbed())
939 if (e
->xcrossing
.mode
== NotifyGrab
||
940 e
->xcrossing
.mode
== NotifyUngrab
||
941 /*ignore enters when we're already in the window */
942 e
->xcrossing
.detail
== NotifyInferior
)
944 ob_debug_type(OB_DEBUG_FOCUS
,
945 "%sNotify mode %d detail %d on %lx IGNORED\n",
946 (e
->type
== EnterNotify
? "Enter" : "Leave"),
948 e
->xcrossing
.detail
, client
?client
->window
:0);
950 ob_debug_type(OB_DEBUG_FOCUS
,
951 "%sNotify mode %d detail %d on %lx, "
952 "focusing window: %d\n",
953 (e
->type
== EnterNotify
? "Enter" : "Leave"),
955 e
->xcrossing
.detail
, (client
?client
->window
:0),
957 if (!nofocus
&& config_focus_follow
)
958 event_enter_client(client
);
966 case ConfigureRequest
:
968 /* dont compress these unless you're going to watch for property
969 notifies in between (these can change what the configure would
971 also you can't compress stacking events
976 /* if nothing is changed, then a configurenotify is needed */
977 gboolean config
= TRUE
;
981 w
= client
->area
.width
;
982 h
= client
->area
.height
;
984 ob_debug("ConfigureRequest desktop %d wmstate %d visibile %d\n",
985 screen_desktop
, client
->wmstate
, client
->frame
->visible
);
987 if (e
->xconfigurerequest
.value_mask
& CWBorderWidth
)
988 if (client
->border_width
!= e
->xconfigurerequest
.border_width
) {
989 client
->border_width
= e
->xconfigurerequest
.border_width
;
990 /* if only the border width is changing, then it's not needed*/
995 if (e
->xconfigurerequest
.value_mask
& CWStackMode
) {
996 ObClient
*sibling
= NULL
;
998 /* get the sibling */
999 if (e
->xconfigurerequest
.value_mask
& CWSibling
) {
1001 win
= g_hash_table_lookup(window_map
,
1002 &e
->xconfigurerequest
.above
);
1003 if (WINDOW_IS_CLIENT(win
) && WINDOW_AS_CLIENT(win
) != client
)
1004 sibling
= WINDOW_AS_CLIENT(win
);
1007 /* activate it rather than just focus it */
1008 stacking_restack_request(client
, sibling
,
1009 e
->xconfigurerequest
.detail
, TRUE
);
1011 /* if a stacking change is requested then it is needed */
1015 /* don't allow clients to move shaded windows (fvwm does this) */
1016 if (client
->shaded
&& (e
->xconfigurerequest
.value_mask
& CWX
||
1017 e
->xconfigurerequest
.value_mask
& CWY
))
1019 e
->xconfigurerequest
.value_mask
&= ~CWX
;
1020 e
->xconfigurerequest
.value_mask
&= ~CWY
;
1022 /* if the client tried to move and we aren't letting it then a
1023 synthetic event is needed */
1027 if (e
->xconfigurerequest
.value_mask
& CWX
||
1028 e
->xconfigurerequest
.value_mask
& CWY
||
1029 e
->xconfigurerequest
.value_mask
& CWWidth
||
1030 e
->xconfigurerequest
.value_mask
& CWHeight
)
1032 if (e
->xconfigurerequest
.value_mask
& CWX
)
1033 x
= e
->xconfigurerequest
.x
;
1034 if (e
->xconfigurerequest
.value_mask
& CWY
)
1035 y
= e
->xconfigurerequest
.y
;
1036 if (e
->xconfigurerequest
.value_mask
& CWWidth
)
1037 w
= e
->xconfigurerequest
.width
;
1038 if (e
->xconfigurerequest
.value_mask
& CWHeight
)
1039 h
= e
->xconfigurerequest
.height
;
1041 /* if a new position or size is requested, then a configure is
1046 ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d\n",
1047 e
->xconfigurerequest
.value_mask
& CWX
, x
,
1048 e
->xconfigurerequest
.value_mask
& CWY
, y
,
1049 e
->xconfigurerequest
.value_mask
& CWWidth
, w
,
1050 e
->xconfigurerequest
.value_mask
& CWHeight
, h
);
1052 /* check for broken apps moving to their root position
1054 XXX remove this some day...that would be nice. right now all
1055 kde apps do this when they try activate themselves on another
1056 desktop. eg. open amarok window on desktop 1, switch to desktop
1057 2, click amarok tray icon. it will move by its decoration size.
1059 if (x
!= client
->area
.x
&&
1060 x
== (client
->frame
->area
.x
+ client
->frame
->size
.left
-
1061 (gint
)client
->border_width
) &&
1062 y
!= client
->area
.y
&&
1063 y
== (client
->frame
->area
.y
+ client
->frame
->size
.top
-
1064 (gint
)client
->border_width
))
1066 ob_debug_type(OB_DEBUG_APP_BUGS
,
1067 "Application %s is trying to move via "
1068 "ConfigureRequest to it's root window position "
1069 "but it is not using StaticGravity\n",
1077 client_find_onscreen(client
, &x
, &y
, w
, h
, FALSE
);
1078 client_configure_full(client
, x
, y
, w
, h
, FALSE
, TRUE
);
1083 if (client
->ignore_unmaps
) {
1084 client
->ignore_unmaps
--;
1087 ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1088 "ignores left %d\n",
1089 client
->window
, e
->xunmap
.event
, e
->xunmap
.from_configure
,
1090 client
->ignore_unmaps
);
1091 client_unmanage(client
);
1093 /* we were trying to focus this window but it's gone */
1094 if (client
== focus_tried
) {
1095 ob_debug_type(OB_DEBUG_FOCUS
, "Tried to focus window 0x%x and it "
1096 "is being unmanaged:\n");
1097 if (XCheckIfEvent(ob_display
, &ce
, look_for_focusin_client
, NULL
)){
1098 XPutBackEvent(ob_display
, &ce
);
1099 ob_debug_type(OB_DEBUG_FOCUS
,
1100 " but another FocusIn is coming\n");
1102 ob_debug_type(OB_DEBUG_FOCUS
,
1103 " so falling back focus again.\n");
1104 focus_tried
= focus_fallback(TRUE
);
1109 ob_debug("DestroyNotify for window 0x%x\n", client
->window
);
1110 client_unmanage(client
);
1112 /* we were trying to focus this window but it's gone */
1113 if (client
== focus_tried
) {
1114 ob_debug_type(OB_DEBUG_FOCUS
, "Tried to focus window 0x%x and it "
1115 "is being unmanaged:\n");
1116 if (XCheckIfEvent(ob_display
, &ce
, look_for_focusin_client
, NULL
)){
1117 XPutBackEvent(ob_display
, &ce
);
1118 ob_debug_type(OB_DEBUG_FOCUS
,
1119 " but another FocusIn is coming\n");
1121 ob_debug_type(OB_DEBUG_FOCUS
,
1122 " so falling back focus again.\n");
1123 focus_tried
= focus_fallback(TRUE
);
1127 case ReparentNotify
:
1128 /* this is when the client is first taken captive in the frame */
1129 if (e
->xreparent
.parent
== client
->frame
->plate
) break;
1132 This event is quite rare and is usually handled in unmapHandler.
1133 However, if the window is unmapped when the reparent event occurs,
1134 the window manager never sees it because an unmap event is not sent
1135 to an already unmapped window.
1138 /* we don't want the reparent event, put it back on the stack for the
1139 X server to deal with after we unmanage the window */
1140 XPutBackEvent(ob_display
, e
);
1142 ob_debug("ReparentNotify for window 0x%x\n", client
->window
);
1143 client_unmanage(client
);
1145 /* we were trying to focus this window but it's gone */
1146 if (client
== focus_tried
) {
1147 ob_debug_type(OB_DEBUG_FOCUS
, "Tried to focus window 0x%x and it "
1148 "is being unmanaged:\n");
1149 if (XCheckIfEvent(ob_display
, &ce
, look_for_focusin_client
, NULL
)){
1150 XPutBackEvent(ob_display
, &ce
);
1151 ob_debug_type(OB_DEBUG_FOCUS
,
1152 " but another FocusIn is coming\n");
1154 ob_debug_type(OB_DEBUG_FOCUS
,
1155 " so falling back focus again.\n");
1156 focus_tried
= focus_fallback(TRUE
);
1161 ob_debug("MapRequest for 0x%lx\n", client
->window
);
1162 if (!client
->iconic
) break; /* this normally doesn't happen, but if it
1163 does, we don't want it!
1164 it can happen now when the window is on
1165 another desktop, but we still don't
1167 client_activate(client
, FALSE
, TRUE
);
1170 /* validate cuz we query stuff off the client here */
1171 if (!client_validate(client
)) break;
1173 if (e
->xclient
.format
!= 32) return;
1175 msgtype
= e
->xclient
.message_type
;
1176 if (msgtype
== prop_atoms
.wm_change_state
) {
1177 /* compress changes into a single change */
1178 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
1180 /* XXX: it would be nice to compress ALL messages of a
1181 type, not just messages in a row without other
1182 message types between. */
1183 if (ce
.xclient
.message_type
!= msgtype
) {
1184 XPutBackEvent(ob_display
, &ce
);
1187 e
->xclient
= ce
.xclient
;
1189 client_set_wm_state(client
, e
->xclient
.data
.l
[0]);
1190 } else if (msgtype
== prop_atoms
.net_wm_desktop
) {
1191 /* compress changes into a single change */
1192 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
1194 /* XXX: it would be nice to compress ALL messages of a
1195 type, not just messages in a row without other
1196 message types between. */
1197 if (ce
.xclient
.message_type
!= msgtype
) {
1198 XPutBackEvent(ob_display
, &ce
);
1201 e
->xclient
= ce
.xclient
;
1203 if ((unsigned)e
->xclient
.data
.l
[0] < screen_num_desktops
||
1204 (unsigned)e
->xclient
.data
.l
[0] == DESKTOP_ALL
)
1205 client_set_desktop(client
, (unsigned)e
->xclient
.data
.l
[0],
1207 } else if (msgtype
== prop_atoms
.net_wm_state
) {
1208 /* can't compress these */
1209 ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1210 (e
->xclient
.data
.l
[0] == 0 ? "Remove" :
1211 e
->xclient
.data
.l
[0] == 1 ? "Add" :
1212 e
->xclient
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
1213 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2],
1215 client_set_state(client
, e
->xclient
.data
.l
[0],
1216 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2]);
1217 } else if (msgtype
== prop_atoms
.net_close_window
) {
1218 ob_debug("net_close_window for 0x%lx\n", client
->window
);
1219 client_close(client
);
1220 } else if (msgtype
== prop_atoms
.net_active_window
) {
1221 ob_debug("net_active_window for 0x%lx source=%s\n",
1223 (e
->xclient
.data
.l
[0] == 0 ? "unknown" :
1224 (e
->xclient
.data
.l
[0] == 1 ? "application" :
1225 (e
->xclient
.data
.l
[0] == 2 ? "user" : "INVALID"))));
1226 /* XXX make use of data.l[2] !? */
1227 event_curtime
= e
->xclient
.data
.l
[1];
1228 if (event_curtime
== 0)
1229 ob_debug_type(OB_DEBUG_APP_BUGS
,
1230 "_NET_ACTIVE_WINDOW message for window %s is "
1231 "missing a timestamp\n", client
->title
);
1232 client_activate(client
, FALSE
,
1233 (e
->xclient
.data
.l
[0] == 0 ||
1234 e
->xclient
.data
.l
[0] == 2));
1235 } else if (msgtype
== prop_atoms
.net_wm_moveresize
) {
1236 ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1237 client
->window
, e
->xclient
.data
.l
[2]);
1238 if ((Atom
)e
->xclient
.data
.l
[2] ==
1239 prop_atoms
.net_wm_moveresize_size_topleft
||
1240 (Atom
)e
->xclient
.data
.l
[2] ==
1241 prop_atoms
.net_wm_moveresize_size_top
||
1242 (Atom
)e
->xclient
.data
.l
[2] ==
1243 prop_atoms
.net_wm_moveresize_size_topright
||
1244 (Atom
)e
->xclient
.data
.l
[2] ==
1245 prop_atoms
.net_wm_moveresize_size_right
||
1246 (Atom
)e
->xclient
.data
.l
[2] ==
1247 prop_atoms
.net_wm_moveresize_size_right
||
1248 (Atom
)e
->xclient
.data
.l
[2] ==
1249 prop_atoms
.net_wm_moveresize_size_bottomright
||
1250 (Atom
)e
->xclient
.data
.l
[2] ==
1251 prop_atoms
.net_wm_moveresize_size_bottom
||
1252 (Atom
)e
->xclient
.data
.l
[2] ==
1253 prop_atoms
.net_wm_moveresize_size_bottomleft
||
1254 (Atom
)e
->xclient
.data
.l
[2] ==
1255 prop_atoms
.net_wm_moveresize_size_left
||
1256 (Atom
)e
->xclient
.data
.l
[2] ==
1257 prop_atoms
.net_wm_moveresize_move
||
1258 (Atom
)e
->xclient
.data
.l
[2] ==
1259 prop_atoms
.net_wm_moveresize_size_keyboard
||
1260 (Atom
)e
->xclient
.data
.l
[2] ==
1261 prop_atoms
.net_wm_moveresize_move_keyboard
) {
1263 moveresize_start(client
, e
->xclient
.data
.l
[0],
1264 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[3],
1265 e
->xclient
.data
.l
[2]);
1267 else if ((Atom
)e
->xclient
.data
.l
[2] ==
1268 prop_atoms
.net_wm_moveresize_cancel
)
1269 moveresize_end(TRUE
);
1270 } else if (msgtype
== prop_atoms
.net_moveresize_window
) {
1271 gint grav
, x
, y
, w
, h
;
1273 if (e
->xclient
.data
.l
[0] & 0xff)
1274 grav
= e
->xclient
.data
.l
[0] & 0xff;
1276 grav
= client
->gravity
;
1278 if (e
->xclient
.data
.l
[0] & 1 << 8)
1279 x
= e
->xclient
.data
.l
[1];
1282 if (e
->xclient
.data
.l
[0] & 1 << 9)
1283 y
= e
->xclient
.data
.l
[2];
1286 if (e
->xclient
.data
.l
[0] & 1 << 10)
1287 w
= e
->xclient
.data
.l
[3];
1289 w
= client
->area
.width
;
1290 if (e
->xclient
.data
.l
[0] & 1 << 11)
1291 h
= e
->xclient
.data
.l
[4];
1293 h
= client
->area
.height
;
1295 ob_debug("MOVERESIZE x %d %d y %d %d\n",
1296 e
->xclient
.data
.l
[0] & 1 << 8, x
,
1297 e
->xclient
.data
.l
[0] & 1 << 9, y
);
1298 client_convert_gravity(client
, grav
, &x
, &y
, w
, h
);
1299 client_find_onscreen(client
, &x
, &y
, w
, h
, FALSE
);
1300 client_configure(client
, x
, y
, w
, h
, FALSE
, TRUE
);
1301 } else if (msgtype
== prop_atoms
.net_restack_window
) {
1302 if (e
->xclient
.data
.l
[0] != 2) {
1303 ob_debug_type(OB_DEBUG_APP_BUGS
,
1304 "_NET_RESTACK_WINDOW sent for window %s with "
1305 "invalid source indication %ld\n",
1306 client
->title
, e
->xclient
.data
.l
[0]);
1308 ObClient
*sibling
= NULL
;
1309 if (e
->xclient
.data
.l
[1]) {
1310 ObWindow
*win
= g_hash_table_lookup(window_map
,
1311 &e
->xclient
.data
.l
[1]);
1312 if (WINDOW_IS_CLIENT(win
) &&
1313 WINDOW_AS_CLIENT(win
) != client
)
1315 sibling
= WINDOW_AS_CLIENT(win
);
1317 if (sibling
== NULL
)
1318 ob_debug_type(OB_DEBUG_APP_BUGS
,
1319 "_NET_RESTACK_WINDOW sent for window %s "
1320 "with invalid sibling 0x%x\n",
1321 client
->title
, e
->xclient
.data
.l
[1]);
1323 if (e
->xclient
.data
.l
[2] == Below
||
1324 e
->xclient
.data
.l
[2] == BottomIf
||
1325 e
->xclient
.data
.l
[2] == Above
||
1326 e
->xclient
.data
.l
[2] == TopIf
||
1327 e
->xclient
.data
.l
[2] == Opposite
)
1329 /* just raise, don't activate */
1330 stacking_restack_request(client
, sibling
,
1331 e
->xclient
.data
.l
[2], FALSE
);
1332 /* send a synthetic ConfigureNotify, cuz this is supposed
1333 to be like a ConfigureRequest. */
1334 client_configure_full(client
, client
->area
.x
,
1337 client
->area
.height
,
1340 ob_debug_type(OB_DEBUG_APP_BUGS
,
1341 "_NET_RESTACK_WINDOW sent for window %s "
1342 "with invalid detail %d\n",
1343 client
->title
, e
->xclient
.data
.l
[2]);
1347 case PropertyNotify
:
1348 /* validate cuz we query stuff off the client here */
1349 if (!client_validate(client
)) break;
1351 /* compress changes to a single property into a single change */
1352 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
1356 /* XXX: it would be nice to compress ALL changes to a property,
1357 not just changes in a row without other props between. */
1359 a
= ce
.xproperty
.atom
;
1360 b
= e
->xproperty
.atom
;
1364 if ((a
== prop_atoms
.net_wm_name
||
1365 a
== prop_atoms
.wm_name
||
1366 a
== prop_atoms
.net_wm_icon_name
||
1367 a
== prop_atoms
.wm_icon_name
)
1369 (b
== prop_atoms
.net_wm_name
||
1370 b
== prop_atoms
.wm_name
||
1371 b
== prop_atoms
.net_wm_icon_name
||
1372 b
== prop_atoms
.wm_icon_name
)) {
1375 if (a
== prop_atoms
.net_wm_icon
&&
1376 b
== prop_atoms
.net_wm_icon
)
1379 XPutBackEvent(ob_display
, &ce
);
1383 msgtype
= e
->xproperty
.atom
;
1384 if (msgtype
== XA_WM_NORMAL_HINTS
) {
1385 client_update_normal_hints(client
);
1386 /* normal hints can make a window non-resizable */
1387 client_setup_decor_and_functions(client
);
1388 } else if (msgtype
== XA_WM_HINTS
) {
1389 client_update_wmhints(client
);
1390 } else if (msgtype
== XA_WM_TRANSIENT_FOR
) {
1391 client_update_transient_for(client
);
1392 client_get_type_and_transientness(client
);
1393 /* type may have changed, so update the layer */
1394 client_calc_layer(client
);
1395 client_setup_decor_and_functions(client
);
1396 } else if (msgtype
== prop_atoms
.net_wm_name
||
1397 msgtype
== prop_atoms
.wm_name
||
1398 msgtype
== prop_atoms
.net_wm_icon_name
||
1399 msgtype
== prop_atoms
.wm_icon_name
) {
1400 client_update_title(client
);
1401 } else if (msgtype
== prop_atoms
.wm_protocols
) {
1402 client_update_protocols(client
);
1403 client_setup_decor_and_functions(client
);
1405 else if (msgtype
== prop_atoms
.net_wm_strut
) {
1406 client_update_strut(client
);
1408 else if (msgtype
== prop_atoms
.net_wm_icon
) {
1409 client_update_icons(client
);
1411 else if (msgtype
== prop_atoms
.net_wm_icon_geometry
) {
1412 client_update_icon_geometry(client
);
1414 else if (msgtype
== prop_atoms
.net_wm_user_time
) {
1415 client_update_user_time(client
);
1417 else if (msgtype
== prop_atoms
.net_wm_user_time_window
) {
1418 client_update_user_time_window(client
);
1421 else if (msgtype
== prop_atoms
.net_wm_sync_request_counter
) {
1422 client_update_sync_request_counter(client
);
1425 case ColormapNotify
:
1426 client_update_colormap(client
, e
->xcolormap
.colormap
);
1431 if (extensions_shape
&& e
->type
== extensions_shape_event_basep
) {
1432 client
->shaped
= ((XShapeEvent
*)e
)->shaped
;
1433 frame_adjust_shape(client
->frame
);
1439 static void event_handle_dock(ObDock
*s
, XEvent
*e
)
1443 if (e
->xbutton
.button
== 1)
1444 stacking_raise(DOCK_AS_WINDOW(s
));
1445 else if (e
->xbutton
.button
== 2)
1446 stacking_lower(DOCK_AS_WINDOW(s
));
1457 static void event_handle_dockapp(ObDockApp
*app
, XEvent
*e
)
1461 dock_app_drag(app
, &e
->xmotion
);
1464 if (app
->ignore_unmaps
) {
1465 app
->ignore_unmaps
--;
1468 dock_remove(app
, TRUE
);
1471 dock_remove(app
, FALSE
);
1473 case ReparentNotify
:
1474 dock_remove(app
, FALSE
);
1476 case ConfigureNotify
:
1477 dock_app_configure(app
, e
->xconfigure
.width
, e
->xconfigure
.height
);
1482 static ObMenuFrame
* find_active_menu()
1485 ObMenuFrame
*ret
= NULL
;
1487 for (it
= menu_frame_visible
; it
; it
= g_list_next(it
)) {
1496 static ObMenuFrame
* find_active_or_last_menu()
1498 ObMenuFrame
*ret
= NULL
;
1500 ret
= find_active_menu();
1501 if (!ret
&& menu_frame_visible
)
1502 ret
= menu_frame_visible
->data
;
1506 static gboolean
event_handle_menu_keyboard(XEvent
*ev
)
1508 guint keycode
, state
;
1511 gboolean ret
= TRUE
;
1513 keycode
= ev
->xkey
.keycode
;
1514 state
= ev
->xkey
.state
;
1515 unikey
= translate_unichar(keycode
);
1517 frame
= find_active_or_last_menu();
1521 else if (keycode
== ob_keycode(OB_KEY_ESCAPE
) && state
== 0) {
1522 /* Escape closes the active menu */
1523 menu_frame_hide(frame
);
1526 else if (keycode
== ob_keycode(OB_KEY_RETURN
) && (state
== 0 ||
1527 state
== ControlMask
))
1529 /* Enter runs the active item or goes into the submenu.
1530 Control-Enter runs it without closing the menu. */
1532 menu_frame_select_next(frame
->child
);
1534 menu_entry_frame_execute(frame
->selected
, state
, ev
->xkey
.time
);
1537 else if (keycode
== ob_keycode(OB_KEY_LEFT
) && ev
->xkey
.state
== 0) {
1538 /* Left goes to the parent menu */
1539 menu_frame_select(frame
, NULL
, TRUE
);
1542 else if (keycode
== ob_keycode(OB_KEY_RIGHT
) && ev
->xkey
.state
== 0) {
1543 /* Right goes to the selected submenu */
1544 if (frame
->child
) menu_frame_select_next(frame
->child
);
1547 else if (keycode
== ob_keycode(OB_KEY_UP
) && state
== 0) {
1548 menu_frame_select_previous(frame
);
1551 else if (keycode
== ob_keycode(OB_KEY_DOWN
) && state
== 0) {
1552 menu_frame_select_next(frame
);
1555 /* keyboard accelerator shortcuts. */
1556 else if (ev
->xkey
.state
== 0 &&
1557 /* was it a valid key? */
1559 /* don't bother if the menu is empty. */
1564 ObMenuEntryFrame
*found
= NULL
;
1565 guint num_found
= 0;
1567 /* start after the selected one */
1568 start
= frame
->entries
;
1569 if (frame
->selected
) {
1570 for (it
= start
; frame
->selected
!= it
->data
; it
= g_list_next(it
))
1571 g_assert(it
!= NULL
); /* nothing was selected? */
1572 /* next with wraparound */
1573 start
= g_list_next(it
);
1574 if (start
== NULL
) start
= frame
->entries
;
1579 ObMenuEntryFrame
*e
= it
->data
;
1580 gunichar entrykey
= 0;
1582 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
)
1583 entrykey
= e
->entry
->data
.normal
.shortcut
;
1584 else if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
)
1585 entrykey
= e
->entry
->data
.submenu
.submenu
->shortcut
;
1587 if (unikey
== entrykey
) {
1588 if (found
== NULL
) found
= e
;
1592 /* next with wraparound */
1593 it
= g_list_next(it
);
1594 if (it
== NULL
) it
= frame
->entries
;
1595 } while (it
!= start
);
1598 if (found
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
1601 menu_frame_select(frame
, found
, TRUE
);
1602 usleep(50000); /* highlight the item for a short bit so the
1603 user can see what happened */
1604 menu_entry_frame_execute(found
, state
, ev
->xkey
.time
);
1606 menu_frame_select(frame
, found
, TRUE
);
1608 menu_frame_select_next(frame
->child
);
1619 static gboolean
event_handle_menu(XEvent
*ev
)
1622 ObMenuEntryFrame
*e
;
1623 gboolean ret
= TRUE
;
1627 if ((ev
->xbutton
.button
< 4 || ev
->xbutton
.button
> 5)
1630 if ((e
= menu_entry_frame_under(ev
->xbutton
.x_root
,
1631 ev
->xbutton
.y_root
)))
1632 menu_entry_frame_execute(e
, ev
->xbutton
.state
,
1635 menu_frame_hide_all();
1639 if ((e
= g_hash_table_lookup(menu_frame_map
, &ev
->xcrossing
.window
))) {
1640 if (e
->ignore_enters
)
1643 menu_frame_select(e
->frame
, e
, FALSE
);
1647 if ((e
= g_hash_table_lookup(menu_frame_map
, &ev
->xcrossing
.window
)) &&
1648 (f
= find_active_menu()) && f
->selected
== e
&&
1649 e
->entry
->type
!= OB_MENU_ENTRY_TYPE_SUBMENU
)
1651 menu_frame_select(e
->frame
, NULL
, FALSE
);
1655 if ((e
= menu_entry_frame_under(ev
->xmotion
.x_root
,
1656 ev
->xmotion
.y_root
)))
1657 menu_frame_select(e
->frame
, e
, FALSE
);
1660 ret
= event_handle_menu_keyboard(ev
);
1666 static void event_handle_user_input(ObClient
*client
, XEvent
*e
)
1668 g_assert(e
->type
== ButtonPress
|| e
->type
== ButtonRelease
||
1669 e
->type
== MotionNotify
|| e
->type
== KeyPress
||
1670 e
->type
== KeyRelease
);
1672 if (menu_frame_visible
) {
1673 if (event_handle_menu(e
))
1674 /* don't use the event if the menu used it, but if the menu
1675 didn't use it and it's a keypress that is bound, it will
1676 close the menu and be used */
1680 /* if the keyboard interactive action uses the event then dont
1681 use it for bindings. likewise is moveresize uses the event. */
1682 if (!keyboard_process_interactive_grab(e
, &client
) &&
1683 !(moveresize_in_progress
&& moveresize_event(e
)))
1685 if (moveresize_in_progress
)
1686 /* make further actions work on the client being
1688 client
= moveresize_client
;
1690 menu_can_hide
= FALSE
;
1691 ob_main_loop_timeout_add(ob_main_loop
,
1692 config_menu_hide_delay
* 1000,
1693 menu_hide_delay_func
,
1694 NULL
, g_direct_equal
, NULL
);
1696 if (e
->type
== ButtonPress
||
1697 e
->type
== ButtonRelease
||
1698 e
->type
== MotionNotify
)
1700 /* the frame may not be "visible" but they can still click on it
1701 in the case where it is animating before disappearing */
1702 if (!client
|| !frame_iconify_animating(client
->frame
))
1703 mouse_event(client
, e
);
1704 } else if (e
->type
== KeyPress
) {
1705 keyboard_event((focus_cycle_target
? focus_cycle_target
:
1706 (client
? client
: focus_client
)), e
);
1711 static gboolean
menu_hide_delay_func(gpointer data
)
1713 menu_can_hide
= TRUE
;
1714 return FALSE
; /* no repeat */
1717 static void focus_delay_dest(gpointer data
)
1722 static gboolean
focus_delay_cmp(gconstpointer d1
, gconstpointer d2
)
1724 const ObFocusDelayData
*f1
= d1
;
1725 return f1
->client
== d2
;
1728 static gboolean
focus_delay_func(gpointer data
)
1730 ObFocusDelayData
*d
= data
;
1731 Time old
= event_curtime
;
1733 event_curtime
= d
->time
;
1734 if (focus_client
!= d
->client
) {
1735 if (client_focus(d
->client
) && config_focus_raise
)
1736 stacking_raise(CLIENT_AS_WINDOW(d
->client
));
1738 event_curtime
= old
;
1739 return FALSE
; /* no repeat */
1742 static void focus_delay_client_dest(ObClient
*client
, gpointer data
)
1744 ob_main_loop_timeout_remove_data(ob_main_loop
, focus_delay_func
,
1748 void event_halt_focus_delay()
1750 ob_main_loop_timeout_remove(ob_main_loop
, focus_delay_func
);
1753 void event_ignore_queued_enters()
1755 GSList
*saved
= NULL
, *it
;
1758 XSync(ob_display
, FALSE
);
1760 /* count the events */
1762 e
= g_new(XEvent
, 1);
1763 if (XCheckTypedEvent(ob_display
, EnterNotify
, e
)) {
1766 win
= g_hash_table_lookup(window_map
, &e
->xany
.window
);
1767 if (win
&& WINDOW_IS_CLIENT(win
))
1768 ++ignore_enter_focus
;
1770 saved
= g_slist_append(saved
, e
);
1776 /* put the events back */
1777 for (it
= saved
; it
; it
= g_slist_next(it
)) {
1778 XPutBackEvent(ob_display
, it
->data
);
1781 g_slist_free(saved
);
1784 gboolean
event_time_after(Time t1
, Time t2
)
1786 g_assert(t1
!= CurrentTime
);
1787 g_assert(t2
!= CurrentTime
);
1790 Timestamp values wrap around (after about 49.7 days). The server, given
1791 its current time is represented by timestamp T, always interprets
1792 timestamps from clients by treating half of the timestamp space as being
1793 later in time than T.
1794 - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1797 /* TIME_HALF is half of the number space of a Time type variable */
1798 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1800 if (t2
>= TIME_HALF
)
1801 /* t2 is in the second half so t1 might wrap around and be smaller than
1803 return t1
>= t2
|| t1
< (t2
+ TIME_HALF
);
1805 /* t2 is in the first half so t1 has to come after it */
1806 return t1
>= t2
&& t1
< (t2
+ TIME_HALF
);