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"
37 #include "framerender.h"
39 #include "moveresize.h"
42 #include "extensions.h"
43 #include "translate.h"
46 #include <X11/keysym.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_group(ObGroup
*g
, 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
;
103 static void ice_handler(gint fd
, gpointer conn
)
106 IceProcessMessages(conn
, NULL
, &b
);
109 static void ice_watch(IceConn conn
, IcePointer data
, Bool opening
,
110 IcePointer
*watch_data
)
115 fd
= IceConnectionNumber(conn
);
116 ob_main_loop_fd_add(ob_main_loop
, fd
, ice_handler
, conn
, NULL
);
118 ob_main_loop_fd_remove(ob_main_loop
, fd
);
124 void event_startup(gboolean reconfig
)
126 if (reconfig
) return;
128 ob_main_loop_x_add(ob_main_loop
, event_process
, NULL
, NULL
);
131 IceAddConnectionWatch(ice_watch
, NULL
);
134 client_add_destructor(focus_delay_client_dest
, NULL
);
137 void event_shutdown(gboolean reconfig
)
139 if (reconfig
) return;
142 IceRemoveConnectionWatch(ice_watch
, NULL
);
145 client_remove_destructor(focus_delay_client_dest
);
148 static Window
event_get_window(XEvent
*e
)
155 window
= RootWindow(ob_display
, ob_screen
);
158 window
= e
->xmap
.window
;
161 window
= e
->xunmap
.window
;
164 window
= e
->xdestroywindow
.window
;
166 case ConfigureRequest
:
167 window
= e
->xconfigurerequest
.window
;
169 case ConfigureNotify
:
170 window
= e
->xconfigure
.window
;
174 if (extensions_xkb
&& e
->type
== extensions_xkb_event_basep
) {
175 switch (((XkbAnyEvent
*)e
)->xkb_type
) {
177 window
= ((XkbBellNotifyEvent
*)e
)->window
;
184 if (extensions_sync
&&
185 e
->type
== extensions_sync_event_basep
+ XSyncAlarmNotify
)
190 window
= e
->xany
.window
;
195 static void event_set_curtime(XEvent
*e
)
197 Time t
= CurrentTime
;
199 /* grab the lasttime and hack up the state */
215 t
= e
->xproperty
.time
;
219 t
= e
->xcrossing
.time
;
223 if (extensions_sync
&&
224 e
->type
== extensions_sync_event_basep
+ XSyncAlarmNotify
)
226 t
= ((XSyncAlarmNotifyEvent
*)e
)->time
;
229 /* if more event types are anticipated, get their timestamp
237 static void event_hack_mods(XEvent
*e
)
240 XkbStateRec xkb_state
;
246 e
->xbutton
.state
= modkeys_only_modifier_masks(e
->xbutton
.state
);
249 e
->xkey
.state
= modkeys_only_modifier_masks(e
->xkey
.state
);
252 e
->xkey
.state
= modkeys_only_modifier_masks(e
->xkey
.state
);
254 if (XkbGetState(ob_display
, XkbUseCoreKbd
, &xkb_state
) == Success
) {
255 e
->xkey
.state
= xkb_state
.compat_state
;
259 /* remove from the state the mask of the modifier key being released,
260 if it is a modifier key being released that is */
261 e
->xkey
.state
&= ~modkeys_keycode_to_mask(e
->xkey
.keycode
);
264 e
->xmotion
.state
= modkeys_only_modifier_masks(e
->xmotion
.state
);
265 /* compress events */
268 while (XCheckTypedWindowEvent(ob_display
, e
->xmotion
.window
,
270 e
->xmotion
.x_root
= ce
.xmotion
.x_root
;
271 e
->xmotion
.y_root
= ce
.xmotion
.y_root
;
278 static gboolean
wanted_focusevent(XEvent
*e
)
280 gint mode
= e
->xfocus
.mode
;
281 gint detail
= e
->xfocus
.detail
;
282 Window win
= e
->xany
.window
;
284 if (e
->type
== FocusIn
) {
286 /* These are ones we never want.. */
288 /* This means focus was given by a keyboard/mouse grab. */
289 if (mode
== NotifyGrab
)
291 /* This means focus was given back from a keyboard/mouse grab. */
292 if (mode
== NotifyUngrab
)
295 /* These are the ones we want.. */
297 if (win
== RootWindow(ob_display
, ob_screen
)) {
298 /* This means focus reverted off of a client */
299 if (detail
== NotifyPointerRoot
|| detail
== NotifyDetailNone
||
300 detail
== NotifyInferior
)
306 /* This means focus moved from the root window to a client */
307 if (detail
== NotifyVirtual
)
309 /* This means focus moved from one client to another */
310 if (detail
== NotifyNonlinearVirtual
)
312 /* This means focus moved to the frame window */
313 if (detail
== NotifyInferior
)
319 g_assert(e
->type
== FocusOut
);
322 /* These are ones we never want.. */
324 /* This means focus was taken by a keyboard/mouse grab. */
325 if (mode
== NotifyGrab
)
328 /* Focus left the root window revertedto state */
329 if (win
== RootWindow(ob_display
, ob_screen
))
332 /* These are the ones we want.. */
334 /* This means focus moved from a client to the root window */
335 if (detail
== NotifyVirtual
)
337 /* This means focus moved from one client to another */
338 if (detail
== NotifyNonlinearVirtual
)
340 /* This means focus had moved to our frame window and now moved off */
341 if (detail
== NotifyNonlinear
)
349 static Bool
look_for_focusin(Display
*d
, XEvent
*e
, XPointer arg
)
351 return e
->type
== FocusIn
&& wanted_focusevent(e
);
354 static gboolean
event_ignore(XEvent
*e
, ObClient
*client
)
358 if (!wanted_focusevent(e
))
362 if (!wanted_focusevent(e
))
369 static void event_process(const XEvent
*ec
, gpointer data
)
372 ObGroup
*group
= NULL
;
373 ObClient
*client
= NULL
;
375 ObDockApp
*dockapp
= NULL
;
376 ObWindow
*obwin
= NULL
;
378 ObEventData
*ed
= data
;
380 /* make a copy we can mangle */
384 window
= event_get_window(e
);
385 if (!(e
->type
== PropertyNotify
&&
386 (group
= g_hash_table_lookup(group_map
, &window
))))
387 if ((obwin
= g_hash_table_lookup(window_map
, &window
))) {
388 switch (obwin
->type
) {
390 dock
= WINDOW_AS_DOCK(obwin
);
393 dockapp
= WINDOW_AS_DOCKAPP(obwin
);
396 client
= WINDOW_AS_CLIENT(obwin
);
399 case Window_Internal
:
400 /* not to be used for events */
401 g_assert_not_reached();
406 event_set_curtime(e
);
408 if (event_ignore(e
, client
)) {
415 /* deal with it in the kernel */
417 if (menu_frame_visible
&&
418 (e
->type
== EnterNotify
|| e
->type
== LeaveNotify
))
420 /* crossing events for menu */
421 event_handle_menu(e
);
422 } else if (e
->type
== FocusIn
) {
423 if (e
->xfocus
.detail
== NotifyPointerRoot
||
424 e
->xfocus
.detail
== NotifyDetailNone
||
425 e
->xfocus
.detail
== NotifyInferior
)
428 ob_debug_type(OB_DEBUG_FOCUS
,
429 "Focus went to pointer root/none or to our frame "
432 /* If another FocusIn is in the queue then don't fallback yet. This
433 fixes the fun case of:
434 window map -> send focusin
435 window unmap -> get focusout
436 window map -> send focusin
437 get first focus out -> fall back to something (new window
438 hasn't received focus yet, so something else) -> send focusin
439 which means the "something else" is the last thing to get a
440 focusin sent to it, so the new window doesn't end up with focus.
442 if (XCheckIfEvent(ob_display
, &ce
, look_for_focusin
, NULL
)) {
443 XPutBackEvent(ob_display
, &ce
);
444 ob_debug_type(OB_DEBUG_FOCUS
,
445 " but another FocusIn is coming\n");
447 /* Focus has been reverted to the root window, nothing, or to
450 FocusOut events come after UnmapNotify, so we don't need to
451 worry about focusing an invalid window
454 /* In this case we know focus is in our screen */
455 if (e
->xfocus
.detail
== NotifyInferior
)
456 focus_left_screen
= FALSE
;
458 if (!focus_left_screen
)
459 focus_fallback(TRUE
);
461 } else if (client
&& client
!= focus_client
) {
462 focus_left_screen
= FALSE
;
463 frame_adjust_focus(client
->frame
, TRUE
);
464 focus_set_client(client
);
465 client_calc_layer(client
);
467 } else if (e
->type
== FocusOut
) {
468 gboolean nomove
= FALSE
;
471 ob_debug_type(OB_DEBUG_FOCUS
, "FocusOut Event\n");
473 /* Look for the followup FocusIn */
474 if (!XCheckIfEvent(ob_display
, &ce
, look_for_focusin
, NULL
)) {
475 /* There is no FocusIn, this means focus went to a window that
476 is not being managed, or a window on another screen. */
480 xerror_set_ignore(TRUE
);
481 if (XGetInputFocus(ob_display
, &win
, &i
) != 0 &&
482 XGetGeometry(ob_display
, win
, &root
, &i
,&i
,&u
,&u
,&u
,&u
) != 0 &&
483 root
!= RootWindow(ob_display
, ob_screen
))
485 ob_debug_type(OB_DEBUG_FOCUS
,
486 "Focus went to another screen !\n");
487 focus_left_screen
= TRUE
;
490 ob_debug_type(OB_DEBUG_FOCUS
,
491 "Focus went to a black hole !\n");
492 xerror_set_ignore(FALSE
);
493 /* nothing is focused */
494 focus_set_client(NULL
);
495 } else if (ce
.xany
.window
== e
->xany
.window
) {
496 ob_debug_type(OB_DEBUG_FOCUS
, "Focus didn't go anywhere\n");
497 /* If focus didn't actually move anywhere, there is nothing to do*/
500 /* Focus did move, so process the FocusIn event */
501 ObEventData ed
= { .ignored
= FALSE
};
502 event_process(&ce
, &ed
);
504 /* The FocusIn was ignored, this means it was on a window
505 that isn't a client. */
506 ob_debug_type(OB_DEBUG_FOCUS
,
507 "Focus went to an unmanaged window 0x%x !\n",
509 focus_fallback(TRUE
);
513 if (client
&& !nomove
) {
514 frame_adjust_focus(client
->frame
, FALSE
);
515 /* focus_set_client has already been called for sure */
516 client_calc_layer(client
);
519 event_handle_group(group
, e
);
521 event_handle_client(client
, e
);
523 event_handle_dockapp(dockapp
, e
);
525 event_handle_dock(dock
, e
);
526 else if (window
== RootWindow(ob_display
, ob_screen
))
527 event_handle_root(e
);
528 else if (e
->type
== MapRequest
)
529 client_manage(window
);
530 else if (e
->type
== ConfigureRequest
) {
531 /* unhandled configure requests must be used to configure the
535 xwc
.x
= e
->xconfigurerequest
.x
;
536 xwc
.y
= e
->xconfigurerequest
.y
;
537 xwc
.width
= e
->xconfigurerequest
.width
;
538 xwc
.height
= e
->xconfigurerequest
.height
;
539 xwc
.border_width
= e
->xconfigurerequest
.border_width
;
540 xwc
.sibling
= e
->xconfigurerequest
.above
;
541 xwc
.stack_mode
= e
->xconfigurerequest
.detail
;
543 /* we are not to be held responsible if someone sends us an
545 xerror_set_ignore(TRUE
);
546 XConfigureWindow(ob_display
, window
,
547 e
->xconfigurerequest
.value_mask
, &xwc
);
548 xerror_set_ignore(FALSE
);
551 else if (extensions_sync
&&
552 e
->type
== extensions_sync_event_basep
+ XSyncAlarmNotify
)
554 XSyncAlarmNotifyEvent
*se
= (XSyncAlarmNotifyEvent
*)e
;
555 if (se
->alarm
== moveresize_alarm
&& moveresize_in_progress
)
560 if (e
->type
== ButtonPress
|| e
->type
== ButtonRelease
||
561 e
->type
== MotionNotify
|| e
->type
== KeyPress
||
562 e
->type
== KeyRelease
)
564 event_handle_user_input(client
, e
);
567 /* if something happens and it's not from an XEvent, then we don't know
569 event_curtime
= CurrentTime
;
572 static void event_handle_root(XEvent
*e
)
578 ob_debug("Another WM has requested to replace us. Exiting.\n");
583 if (e
->xclient
.format
!= 32) break;
585 msgtype
= e
->xclient
.message_type
;
586 if (msgtype
== prop_atoms
.net_current_desktop
) {
587 guint d
= e
->xclient
.data
.l
[0];
588 if (d
< screen_num_desktops
) {
589 event_curtime
= e
->xclient
.data
.l
[1];
590 if (event_curtime
== 0)
591 ob_debug_type(OB_DEBUG_APP_BUGS
,
592 "_NET_CURRENT_DESKTOP message is missing "
594 screen_set_desktop(d
);
596 } else if (msgtype
== prop_atoms
.net_number_of_desktops
) {
597 guint d
= e
->xclient
.data
.l
[0];
599 screen_set_num_desktops(d
);
600 } else if (msgtype
== prop_atoms
.net_showing_desktop
) {
601 screen_show_desktop(e
->xclient
.data
.l
[0] != 0, TRUE
);
602 } else if (msgtype
== prop_atoms
.openbox_control
) {
603 if (e
->xclient
.data
.l
[0] == 1)
605 else if (e
->xclient
.data
.l
[0] == 2)
610 if (e
->xproperty
.atom
== prop_atoms
.net_desktop_names
)
611 screen_update_desktop_names();
612 else if (e
->xproperty
.atom
== prop_atoms
.net_desktop_layout
)
613 screen_update_layout();
615 case ConfigureNotify
:
617 XRRUpdateConfiguration(e
);
626 static void event_handle_group(ObGroup
*group
, XEvent
*e
)
630 g_assert(e
->type
== PropertyNotify
);
632 for (it
= group
->members
; it
; it
= g_slist_next(it
))
633 event_handle_client(it
->data
, e
);
636 void event_enter_client(ObClient
*client
)
638 g_assert(config_focus_follow
);
640 if (client_normal(client
) && client_can_focus(client
)) {
641 if (config_focus_delay
) {
642 ObFocusDelayData
*data
;
644 ob_main_loop_timeout_remove(ob_main_loop
, focus_delay_func
);
646 data
= g_new(ObFocusDelayData
, 1);
647 data
->client
= client
;
648 data
->time
= event_curtime
;
650 ob_main_loop_timeout_add(ob_main_loop
,
653 data
, focus_delay_cmp
, focus_delay_dest
);
655 ObFocusDelayData data
;
656 data
.client
= client
;
657 data
.time
= event_curtime
;
658 focus_delay_func(&data
);
663 static void event_handle_client(ObClient
*client
, XEvent
*e
)
672 /* Wheel buttons don't draw because they are an instant click, so it
673 is a waste of resources to go drawing it. */
674 if (!(e
->xbutton
.button
== 4 || e
->xbutton
.button
== 5)) {
675 con
= frame_context(client
, e
->xbutton
.window
);
676 con
= mouse_button_frame_context(con
, e
->xbutton
.button
);
678 case OB_FRAME_CONTEXT_MAXIMIZE
:
679 client
->frame
->max_press
= (e
->type
== ButtonPress
);
680 framerender_frame(client
->frame
);
682 case OB_FRAME_CONTEXT_CLOSE
:
683 client
->frame
->close_press
= (e
->type
== ButtonPress
);
684 framerender_frame(client
->frame
);
686 case OB_FRAME_CONTEXT_ICONIFY
:
687 client
->frame
->iconify_press
= (e
->type
== ButtonPress
);
688 framerender_frame(client
->frame
);
690 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
691 client
->frame
->desk_press
= (e
->type
== ButtonPress
);
692 framerender_frame(client
->frame
);
694 case OB_FRAME_CONTEXT_SHADE
:
695 client
->frame
->shade_press
= (e
->type
== ButtonPress
);
696 framerender_frame(client
->frame
);
699 /* nothing changes with clicks for any other contexts */
705 con
= frame_context(client
, e
->xcrossing
.window
);
707 case OB_FRAME_CONTEXT_MAXIMIZE
:
708 client
->frame
->max_hover
= FALSE
;
709 frame_adjust_state(client
->frame
);
711 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
712 client
->frame
->desk_hover
= FALSE
;
713 frame_adjust_state(client
->frame
);
715 case OB_FRAME_CONTEXT_SHADE
:
716 client
->frame
->shade_hover
= FALSE
;
717 frame_adjust_state(client
->frame
);
719 case OB_FRAME_CONTEXT_ICONIFY
:
720 client
->frame
->iconify_hover
= FALSE
;
721 frame_adjust_state(client
->frame
);
723 case OB_FRAME_CONTEXT_CLOSE
:
724 client
->frame
->close_hover
= FALSE
;
725 frame_adjust_state(client
->frame
);
727 case OB_FRAME_CONTEXT_FRAME
:
728 /* When the mouse leaves an animating window, don't use the
729 corresponding enter events. Pretend like the animating window
730 doesn't even exist..! */
731 if (frame_iconify_animating(client
->frame
))
732 event_ignore_queued_enters();
734 ob_debug_type(OB_DEBUG_FOCUS
,
735 "%sNotify mode %d detail %d on %lx\n",
736 (e
->type
== EnterNotify
? "Enter" : "Leave"),
738 e
->xcrossing
.detail
, (client
?client
->window
:0));
739 if (keyboard_interactively_grabbed())
741 if (config_focus_follow
&& config_focus_delay
&&
742 /* leave inferior events can happen when the mouse goes onto
743 the window's border and then into the window before the
745 e
->xcrossing
.detail
!= NotifyInferior
)
747 ob_main_loop_timeout_remove_data(ob_main_loop
,
758 gboolean nofocus
= FALSE
;
760 if (ignore_enter_focus
) {
761 ignore_enter_focus
--;
765 con
= frame_context(client
, e
->xcrossing
.window
);
767 case OB_FRAME_CONTEXT_MAXIMIZE
:
768 client
->frame
->max_hover
= TRUE
;
769 frame_adjust_state(client
->frame
);
771 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
772 client
->frame
->desk_hover
= TRUE
;
773 frame_adjust_state(client
->frame
);
775 case OB_FRAME_CONTEXT_SHADE
:
776 client
->frame
->shade_hover
= TRUE
;
777 frame_adjust_state(client
->frame
);
779 case OB_FRAME_CONTEXT_ICONIFY
:
780 client
->frame
->iconify_hover
= TRUE
;
781 frame_adjust_state(client
->frame
);
783 case OB_FRAME_CONTEXT_CLOSE
:
784 client
->frame
->close_hover
= TRUE
;
785 frame_adjust_state(client
->frame
);
787 case OB_FRAME_CONTEXT_FRAME
:
788 if (keyboard_interactively_grabbed())
790 if (e
->xcrossing
.mode
== NotifyGrab
||
791 e
->xcrossing
.mode
== NotifyUngrab
||
792 /*ignore enters when we're already in the window */
793 e
->xcrossing
.detail
== NotifyInferior
)
795 ob_debug_type(OB_DEBUG_FOCUS
,
796 "%sNotify mode %d detail %d on %lx IGNORED\n",
797 (e
->type
== EnterNotify
? "Enter" : "Leave"),
799 e
->xcrossing
.detail
, client
?client
->window
:0);
801 ob_debug_type(OB_DEBUG_FOCUS
,
802 "%sNotify mode %d detail %d on %lx, "
803 "focusing window: %d\n",
804 (e
->type
== EnterNotify
? "Enter" : "Leave"),
806 e
->xcrossing
.detail
, (client
?client
->window
:0),
808 if (!nofocus
&& config_focus_follow
)
809 event_enter_client(client
);
817 case ConfigureRequest
:
818 /* dont compress these unless you're going to watch for property
819 notifies in between (these can change what the configure would
821 also you can't compress stacking events
824 ob_debug("ConfigureRequest desktop %d wmstate %d vis %d\n",
825 screen_desktop
, client
->wmstate
, client
->frame
->visible
);
827 /* don't allow clients to move shaded windows (fvwm does this) */
828 if (client
->shaded
) {
829 e
->xconfigurerequest
.value_mask
&= ~CWX
;
830 e
->xconfigurerequest
.value_mask
&= ~CWY
;
833 /* resize, then move, as specified in the EWMH section 7.7 */
834 if (e
->xconfigurerequest
.value_mask
& (CWWidth
| CWHeight
|
839 if (e
->xconfigurerequest
.value_mask
& CWBorderWidth
)
840 client
->border_width
= e
->xconfigurerequest
.border_width
;
842 x
= (e
->xconfigurerequest
.value_mask
& CWX
) ?
843 e
->xconfigurerequest
.x
: client
->area
.x
;
844 y
= (e
->xconfigurerequest
.value_mask
& CWY
) ?
845 e
->xconfigurerequest
.y
: client
->area
.y
;
846 w
= (e
->xconfigurerequest
.value_mask
& CWWidth
) ?
847 e
->xconfigurerequest
.width
: client
->area
.width
;
848 h
= (e
->xconfigurerequest
.value_mask
& CWHeight
) ?
849 e
->xconfigurerequest
.height
: client
->area
.height
;
851 ob_debug("ConfigureRequest x %d %d y %d %d\n",
852 e
->xconfigurerequest
.value_mask
& CWX
, x
,
853 e
->xconfigurerequest
.value_mask
& CWY
, y
);
855 /* check for broken apps moving to their root position
857 XXX remove this some day...that would be nice. right now all
858 kde apps do this when they try activate themselves on another
859 desktop. eg. open amarok window on desktop 1, switch to desktop
860 2, click amarok tray icon. it will move by its decoration size.
862 if (x
!= client
->area
.x
&&
863 x
== (client
->frame
->area
.x
+ client
->frame
->size
.left
-
864 (gint
)client
->border_width
) &&
865 y
!= client
->area
.y
&&
866 y
== (client
->frame
->area
.y
+ client
->frame
->size
.top
-
867 (gint
)client
->border_width
))
869 ob_debug_type(OB_DEBUG_APP_BUGS
,
870 "Application %s is trying to move via "
871 "ConfigureRequest to it's root window position "
872 "but it is not using StaticGravity\n",
879 client_find_onscreen(client
, &x
, &y
, w
, h
, FALSE
);
880 client_configure_full(client
, x
, y
, w
, h
, FALSE
, TRUE
, TRUE
);
883 if (e
->xconfigurerequest
.value_mask
& CWStackMode
) {
884 switch (e
->xconfigurerequest
.detail
) {
887 /* Apps are so rude. And this is totally disconnected from
888 activation/focus. Bleh. */
889 /*client_lower(client);*/
895 /* Apps are so rude. And this is totally disconnected from
896 activation/focus. Bleh. */
897 /*client_raise(client);*/
903 if (client
->ignore_unmaps
) {
904 client
->ignore_unmaps
--;
907 ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
909 client
->window
, e
->xunmap
.event
, e
->xunmap
.from_configure
,
910 client
->ignore_unmaps
);
911 client_unmanage(client
);
914 ob_debug("DestroyNotify for window 0x%x\n", client
->window
);
915 client_unmanage(client
);
918 /* this is when the client is first taken captive in the frame */
919 if (e
->xreparent
.parent
== client
->frame
->plate
) break;
922 This event is quite rare and is usually handled in unmapHandler.
923 However, if the window is unmapped when the reparent event occurs,
924 the window manager never sees it because an unmap event is not sent
925 to an already unmapped window.
928 /* we don't want the reparent event, put it back on the stack for the
929 X server to deal with after we unmanage the window */
930 XPutBackEvent(ob_display
, e
);
932 ob_debug("ReparentNotify for window 0x%x\n", client
->window
);
933 client_unmanage(client
);
936 ob_debug("MapRequest for 0x%lx\n", client
->window
);
937 if (!client
->iconic
) break; /* this normally doesn't happen, but if it
938 does, we don't want it!
939 it can happen now when the window is on
940 another desktop, but we still don't
942 client_activate(client
, FALSE
, TRUE
);
945 /* validate cuz we query stuff off the client here */
946 if (!client_validate(client
)) break;
948 if (e
->xclient
.format
!= 32) return;
950 msgtype
= e
->xclient
.message_type
;
951 if (msgtype
== prop_atoms
.wm_change_state
) {
952 /* compress changes into a single change */
953 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
955 /* XXX: it would be nice to compress ALL messages of a
956 type, not just messages in a row without other
957 message types between. */
958 if (ce
.xclient
.message_type
!= msgtype
) {
959 XPutBackEvent(ob_display
, &ce
);
962 e
->xclient
= ce
.xclient
;
964 client_set_wm_state(client
, e
->xclient
.data
.l
[0]);
965 } else if (msgtype
== prop_atoms
.net_wm_desktop
) {
966 /* compress changes into a single change */
967 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
969 /* XXX: it would be nice to compress ALL messages of a
970 type, not just messages in a row without other
971 message types between. */
972 if (ce
.xclient
.message_type
!= msgtype
) {
973 XPutBackEvent(ob_display
, &ce
);
976 e
->xclient
= ce
.xclient
;
978 if ((unsigned)e
->xclient
.data
.l
[0] < screen_num_desktops
||
979 (unsigned)e
->xclient
.data
.l
[0] == DESKTOP_ALL
)
980 client_set_desktop(client
, (unsigned)e
->xclient
.data
.l
[0],
982 } else if (msgtype
== prop_atoms
.net_wm_state
) {
983 /* can't compress these */
984 ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
985 (e
->xclient
.data
.l
[0] == 0 ? "Remove" :
986 e
->xclient
.data
.l
[0] == 1 ? "Add" :
987 e
->xclient
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
988 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2],
990 client_set_state(client
, e
->xclient
.data
.l
[0],
991 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2]);
992 } else if (msgtype
== prop_atoms
.net_close_window
) {
993 ob_debug("net_close_window for 0x%lx\n", client
->window
);
994 client_close(client
);
995 } else if (msgtype
== prop_atoms
.net_active_window
) {
996 ob_debug("net_active_window for 0x%lx source=%s\n",
998 (e
->xclient
.data
.l
[0] == 0 ? "unknown" :
999 (e
->xclient
.data
.l
[0] == 1 ? "application" :
1000 (e
->xclient
.data
.l
[0] == 2 ? "user" : "INVALID"))));
1001 /* XXX make use of data.l[2] !? */
1002 event_curtime
= e
->xclient
.data
.l
[1];
1003 ob_debug_type(OB_DEBUG_APP_BUGS
,
1004 "_NET_ACTIVE_WINDOW message for window %s is "
1005 "missing a timestamp\n", client
->title
);
1006 client_activate(client
, FALSE
,
1007 (e
->xclient
.data
.l
[0] == 0 ||
1008 e
->xclient
.data
.l
[0] == 2));
1009 } else if (msgtype
== prop_atoms
.net_wm_moveresize
) {
1010 ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1011 client
->window
, e
->xclient
.data
.l
[2]);
1012 if ((Atom
)e
->xclient
.data
.l
[2] ==
1013 prop_atoms
.net_wm_moveresize_size_topleft
||
1014 (Atom
)e
->xclient
.data
.l
[2] ==
1015 prop_atoms
.net_wm_moveresize_size_top
||
1016 (Atom
)e
->xclient
.data
.l
[2] ==
1017 prop_atoms
.net_wm_moveresize_size_topright
||
1018 (Atom
)e
->xclient
.data
.l
[2] ==
1019 prop_atoms
.net_wm_moveresize_size_right
||
1020 (Atom
)e
->xclient
.data
.l
[2] ==
1021 prop_atoms
.net_wm_moveresize_size_right
||
1022 (Atom
)e
->xclient
.data
.l
[2] ==
1023 prop_atoms
.net_wm_moveresize_size_bottomright
||
1024 (Atom
)e
->xclient
.data
.l
[2] ==
1025 prop_atoms
.net_wm_moveresize_size_bottom
||
1026 (Atom
)e
->xclient
.data
.l
[2] ==
1027 prop_atoms
.net_wm_moveresize_size_bottomleft
||
1028 (Atom
)e
->xclient
.data
.l
[2] ==
1029 prop_atoms
.net_wm_moveresize_size_left
||
1030 (Atom
)e
->xclient
.data
.l
[2] ==
1031 prop_atoms
.net_wm_moveresize_move
||
1032 (Atom
)e
->xclient
.data
.l
[2] ==
1033 prop_atoms
.net_wm_moveresize_size_keyboard
||
1034 (Atom
)e
->xclient
.data
.l
[2] ==
1035 prop_atoms
.net_wm_moveresize_move_keyboard
) {
1037 moveresize_start(client
, e
->xclient
.data
.l
[0],
1038 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[3],
1039 e
->xclient
.data
.l
[2]);
1041 else if ((Atom
)e
->xclient
.data
.l
[2] ==
1042 prop_atoms
.net_wm_moveresize_cancel
)
1043 moveresize_end(TRUE
);
1044 } else if (msgtype
== prop_atoms
.net_moveresize_window
) {
1045 gint grav
, x
, y
, w
, h
;
1047 if (e
->xclient
.data
.l
[0] & 0xff)
1048 grav
= e
->xclient
.data
.l
[0] & 0xff;
1050 grav
= client
->gravity
;
1052 if (e
->xclient
.data
.l
[0] & 1 << 8)
1053 x
= e
->xclient
.data
.l
[1];
1056 if (e
->xclient
.data
.l
[0] & 1 << 9)
1057 y
= e
->xclient
.data
.l
[2];
1060 if (e
->xclient
.data
.l
[0] & 1 << 10)
1061 w
= e
->xclient
.data
.l
[3];
1063 w
= client
->area
.width
;
1064 if (e
->xclient
.data
.l
[0] & 1 << 11)
1065 h
= e
->xclient
.data
.l
[4];
1067 h
= client
->area
.height
;
1069 ob_debug("MOVERESIZE x %d %d y %d %d\n",
1070 e
->xclient
.data
.l
[0] & 1 << 8, x
,
1071 e
->xclient
.data
.l
[0] & 1 << 9, y
);
1072 client_convert_gravity(client
, grav
, &x
, &y
, w
, h
);
1073 client_find_onscreen(client
, &x
, &y
, w
, h
, FALSE
);
1074 client_configure(client
, x
, y
, w
, h
, FALSE
, TRUE
);
1077 case PropertyNotify
:
1078 /* validate cuz we query stuff off the client here */
1079 if (!client_validate(client
)) break;
1081 /* compress changes to a single property into a single change */
1082 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
1086 /* XXX: it would be nice to compress ALL changes to a property,
1087 not just changes in a row without other props between. */
1089 a
= ce
.xproperty
.atom
;
1090 b
= e
->xproperty
.atom
;
1094 if ((a
== prop_atoms
.net_wm_name
||
1095 a
== prop_atoms
.wm_name
||
1096 a
== prop_atoms
.net_wm_icon_name
||
1097 a
== prop_atoms
.wm_icon_name
)
1099 (b
== prop_atoms
.net_wm_name
||
1100 b
== prop_atoms
.wm_name
||
1101 b
== prop_atoms
.net_wm_icon_name
||
1102 b
== prop_atoms
.wm_icon_name
)) {
1105 if (a
== prop_atoms
.net_wm_icon
&&
1106 b
== prop_atoms
.net_wm_icon
)
1109 XPutBackEvent(ob_display
, &ce
);
1113 msgtype
= e
->xproperty
.atom
;
1114 if (msgtype
== XA_WM_NORMAL_HINTS
) {
1115 client_update_normal_hints(client
);
1116 /* normal hints can make a window non-resizable */
1117 client_setup_decor_and_functions(client
);
1118 } else if (msgtype
== XA_WM_HINTS
) {
1119 client_update_wmhints(client
);
1120 } else if (msgtype
== XA_WM_TRANSIENT_FOR
) {
1121 client_update_transient_for(client
);
1122 client_get_type(client
);
1123 /* type may have changed, so update the layer */
1124 client_calc_layer(client
);
1125 client_setup_decor_and_functions(client
);
1126 } else if (msgtype
== prop_atoms
.net_wm_name
||
1127 msgtype
== prop_atoms
.wm_name
||
1128 msgtype
== prop_atoms
.net_wm_icon_name
||
1129 msgtype
== prop_atoms
.wm_icon_name
) {
1130 client_update_title(client
);
1131 } else if (msgtype
== prop_atoms
.wm_class
) {
1132 client_update_class(client
);
1133 } else if (msgtype
== prop_atoms
.wm_protocols
) {
1134 client_update_protocols(client
);
1135 client_setup_decor_and_functions(client
);
1137 else if (msgtype
== prop_atoms
.net_wm_strut
) {
1138 client_update_strut(client
);
1140 else if (msgtype
== prop_atoms
.net_wm_icon
) {
1141 client_update_icons(client
);
1143 else if (msgtype
== prop_atoms
.net_wm_icon_geometry
) {
1144 client_update_icon_geometry(client
);
1146 else if (msgtype
== prop_atoms
.net_wm_user_time
) {
1147 client_update_user_time(client
);
1150 else if (msgtype
== prop_atoms
.net_wm_sync_request_counter
) {
1151 client_update_sync_request_counter(client
);
1154 else if (msgtype
== prop_atoms
.sm_client_id
) {
1155 client_update_sm_client_id(client
);
1157 case ColormapNotify
:
1158 client_update_colormap(client
, e
->xcolormap
.colormap
);
1163 if (extensions_shape
&& e
->type
== extensions_shape_event_basep
) {
1164 client
->shaped
= ((XShapeEvent
*)e
)->shaped
;
1165 frame_adjust_shape(client
->frame
);
1171 static void event_handle_dock(ObDock
*s
, XEvent
*e
)
1175 if (e
->xbutton
.button
== 1)
1176 stacking_raise(DOCK_AS_WINDOW(s
));
1177 else if (e
->xbutton
.button
== 2)
1178 stacking_lower(DOCK_AS_WINDOW(s
));
1189 static void event_handle_dockapp(ObDockApp
*app
, XEvent
*e
)
1193 dock_app_drag(app
, &e
->xmotion
);
1196 if (app
->ignore_unmaps
) {
1197 app
->ignore_unmaps
--;
1200 dock_remove(app
, TRUE
);
1203 dock_remove(app
, FALSE
);
1205 case ReparentNotify
:
1206 dock_remove(app
, FALSE
);
1208 case ConfigureNotify
:
1209 dock_app_configure(app
, e
->xconfigure
.width
, e
->xconfigure
.height
);
1214 static ObMenuFrame
* find_active_menu()
1217 ObMenuFrame
*ret
= NULL
;
1219 for (it
= menu_frame_visible
; it
; it
= g_list_next(it
)) {
1228 static ObMenuFrame
* find_active_or_last_menu()
1230 ObMenuFrame
*ret
= NULL
;
1232 ret
= find_active_menu();
1233 if (!ret
&& menu_frame_visible
)
1234 ret
= menu_frame_visible
->data
;
1238 static gboolean
event_handle_menu_keyboard(XEvent
*ev
)
1240 guint keycode
, state
;
1243 gboolean ret
= TRUE
;
1245 keycode
= ev
->xkey
.keycode
;
1246 state
= ev
->xkey
.state
;
1247 unikey
= translate_unichar(keycode
);
1249 frame
= find_active_or_last_menu();
1253 else if (keycode
== ob_keycode(OB_KEY_ESCAPE
) && state
== 0) {
1254 /* Escape closes the active menu */
1255 menu_frame_hide(frame
);
1258 else if (keycode
== ob_keycode(OB_KEY_RETURN
) && (state
== 0 ||
1259 state
== ControlMask
))
1261 /* Enter runs the active item or goes into the submenu.
1262 Control-Enter runs it without closing the menu. */
1264 menu_frame_select_next(frame
->child
);
1266 menu_entry_frame_execute(frame
->selected
, state
, ev
->xkey
.time
);
1269 else if (keycode
== ob_keycode(OB_KEY_LEFT
) && ev
->xkey
.state
== 0) {
1270 /* Left goes to the parent menu */
1271 menu_frame_select(frame
, NULL
, TRUE
);
1274 else if (keycode
== ob_keycode(OB_KEY_RIGHT
) && ev
->xkey
.state
== 0) {
1275 /* Right goes to the selected submenu */
1276 if (frame
->child
) menu_frame_select_next(frame
->child
);
1279 else if (keycode
== ob_keycode(OB_KEY_UP
) && state
== 0) {
1280 menu_frame_select_previous(frame
);
1283 else if (keycode
== ob_keycode(OB_KEY_DOWN
) && state
== 0) {
1284 menu_frame_select_next(frame
);
1287 /* keyboard accelerator shortcuts. */
1288 else if (ev
->xkey
.state
== 0 &&
1289 /* was it a valid key? */
1291 /* don't bother if the menu is empty. */
1296 ObMenuEntryFrame
*found
= NULL
;
1297 guint num_found
= 0;
1299 /* start after the selected one */
1300 start
= frame
->entries
;
1301 if (frame
->selected
) {
1302 for (it
= start
; frame
->selected
!= it
->data
; it
= g_list_next(it
))
1303 g_assert(it
!= NULL
); /* nothing was selected? */
1304 /* next with wraparound */
1305 start
= g_list_next(it
);
1306 if (start
== NULL
) start
= frame
->entries
;
1311 ObMenuEntryFrame
*e
= it
->data
;
1312 gunichar entrykey
= 0;
1314 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
)
1315 entrykey
= e
->entry
->data
.normal
.shortcut
;
1316 else if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
)
1317 entrykey
= e
->entry
->data
.submenu
.submenu
->shortcut
;
1319 if (unikey
== entrykey
) {
1320 if (found
== NULL
) found
= e
;
1324 /* next with wraparound */
1325 it
= g_list_next(it
);
1326 if (it
== NULL
) it
= frame
->entries
;
1327 } while (it
!= start
);
1330 if (found
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
1333 menu_frame_select(frame
, found
, TRUE
);
1334 usleep(50000); /* highlight the item for a short bit so the
1335 user can see what happened */
1336 menu_entry_frame_execute(found
, state
, ev
->xkey
.time
);
1338 menu_frame_select(frame
, found
, TRUE
);
1340 menu_frame_select_next(frame
->child
);
1351 static gboolean
event_handle_menu(XEvent
*ev
)
1354 ObMenuEntryFrame
*e
;
1355 gboolean ret
= TRUE
;
1359 if ((ev
->xbutton
.button
< 4 || ev
->xbutton
.button
> 5)
1362 if ((e
= menu_entry_frame_under(ev
->xbutton
.x_root
,
1363 ev
->xbutton
.y_root
)))
1364 menu_entry_frame_execute(e
, ev
->xbutton
.state
,
1367 menu_frame_hide_all();
1371 if ((e
= g_hash_table_lookup(menu_frame_map
, &ev
->xcrossing
.window
))) {
1372 if (e
->ignore_enters
)
1375 menu_frame_select(e
->frame
, e
, FALSE
);
1379 if ((e
= g_hash_table_lookup(menu_frame_map
, &ev
->xcrossing
.window
)) &&
1380 (f
= find_active_menu()) && f
->selected
== e
&&
1381 e
->entry
->type
!= OB_MENU_ENTRY_TYPE_SUBMENU
)
1383 menu_frame_select(e
->frame
, NULL
, FALSE
);
1386 if ((e
= menu_entry_frame_under(ev
->xmotion
.x_root
,
1387 ev
->xmotion
.y_root
)))
1388 menu_frame_select(e
->frame
, e
, FALSE
);
1391 ret
= event_handle_menu_keyboard(ev
);
1397 static void event_handle_user_input(ObClient
*client
, XEvent
*e
)
1399 g_assert(e
->type
== ButtonPress
|| e
->type
== ButtonRelease
||
1400 e
->type
== MotionNotify
|| e
->type
== KeyPress
||
1401 e
->type
== KeyRelease
);
1403 if (menu_frame_visible
) {
1404 if (event_handle_menu(e
))
1405 /* don't use the event if the menu used it, but if the menu
1406 didn't use it and it's a keypress that is bound, it will
1407 close the menu and be used */
1411 /* if the keyboard interactive action uses the event then dont
1412 use it for bindings. likewise is moveresize uses the event. */
1413 if (!keyboard_process_interactive_grab(e
, &client
) &&
1414 !(moveresize_in_progress
&& moveresize_event(e
)))
1416 if (moveresize_in_progress
)
1417 /* make further actions work on the client being
1419 client
= moveresize_client
;
1421 menu_can_hide
= FALSE
;
1422 ob_main_loop_timeout_add(ob_main_loop
,
1423 config_menu_hide_delay
* 1000,
1424 menu_hide_delay_func
,
1425 NULL
, g_direct_equal
, NULL
);
1427 if (e
->type
== ButtonPress
||
1428 e
->type
== ButtonRelease
||
1429 e
->type
== MotionNotify
)
1431 /* the frame may not be "visible" but they can still click on it
1432 in the case where it is animating before disappearing */
1433 if (!client
|| !frame_iconify_animating(client
->frame
))
1434 mouse_event(client
, e
);
1435 } else if (e
->type
== KeyPress
) {
1436 keyboard_event((focus_cycle_target
? focus_cycle_target
:
1437 (client
? client
: focus_client
)), e
);
1442 static gboolean
menu_hide_delay_func(gpointer data
)
1444 menu_can_hide
= TRUE
;
1445 return FALSE
; /* no repeat */
1448 static void focus_delay_dest(gpointer data
)
1453 static gboolean
focus_delay_cmp(gconstpointer d1
, gconstpointer d2
)
1455 const ObFocusDelayData
*f1
= d1
;
1456 return f1
->client
== d2
;
1459 static gboolean
focus_delay_func(gpointer data
)
1461 ObFocusDelayData
*d
= data
;
1462 Time old
= event_curtime
;
1464 event_curtime
= d
->time
;
1465 if (focus_client
!= d
->client
) {
1466 if (client_focus(d
->client
) && config_focus_raise
)
1467 client_raise(d
->client
);
1469 event_curtime
= old
;
1470 return FALSE
; /* no repeat */
1473 static void focus_delay_client_dest(ObClient
*client
, gpointer data
)
1475 ob_main_loop_timeout_remove_data(ob_main_loop
, focus_delay_func
,
1479 void event_halt_focus_delay()
1481 ob_main_loop_timeout_remove(ob_main_loop
, focus_delay_func
);
1484 void event_ignore_queued_enters()
1486 GSList
*saved
= NULL
, *it
;
1489 XSync(ob_display
, FALSE
);
1491 /* count the events */
1493 e
= g_new(XEvent
, 1);
1494 if (XCheckTypedEvent(ob_display
, EnterNotify
, e
)) {
1497 win
= g_hash_table_lookup(window_map
, &e
->xany
.window
);
1498 if (win
&& WINDOW_IS_CLIENT(win
))
1499 ++ignore_enter_focus
;
1501 saved
= g_slist_append(saved
, e
);
1507 /* put the events back */
1508 for (it
= saved
; it
; it
= g_slist_next(it
)) {
1509 XPutBackEvent(ob_display
, it
->data
);
1512 g_slist_free(saved
);
1515 gboolean
event_time_after(Time t1
, Time t2
)
1517 g_assert(t1
!= CurrentTime
);
1518 g_assert(t2
!= CurrentTime
);
1521 Timestamp values wrap around (after about 49.7 days). The server, given
1522 its current time is represented by timestamp T, always interprets
1523 timestamps from clients by treating half of the timestamp space as being
1524 later in time than T.
1525 - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1528 /* TIME_HALF is half of the number space of a Time type variable */
1529 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1531 if (t2
>= TIME_HALF
)
1532 /* t2 is in the second half so t1 might wrap around and be smaller than
1534 return t1
>= t2
|| t1
< (t2
+ TIME_HALF
);
1536 /* t2 is in the first half so t1 has to come after it */
1537 return t1
>= t2
&& t1
< (t2
+ TIME_HALF
);