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
, TRUE
);
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_command
) {
1132 client_update_command(client
);
1133 } else if (msgtype
== prop_atoms
.wm_class
) {
1134 client_update_class(client
);
1135 } else if (msgtype
== prop_atoms
.wm_protocols
) {
1136 client_update_protocols(client
);
1137 client_setup_decor_and_functions(client
);
1139 else if (msgtype
== prop_atoms
.net_wm_strut
) {
1140 client_update_strut(client
);
1142 else if (msgtype
== prop_atoms
.net_wm_icon
) {
1143 client_update_icons(client
);
1145 else if (msgtype
== prop_atoms
.net_wm_icon_geometry
) {
1146 client_update_icon_geometry(client
);
1148 else if (msgtype
== prop_atoms
.net_wm_user_time
) {
1149 client_update_user_time(client
);
1152 else if (msgtype
== prop_atoms
.net_wm_sync_request_counter
) {
1153 client_update_sync_request_counter(client
);
1156 else if (msgtype
== prop_atoms
.sm_client_id
) {
1157 client_update_sm_client_id(client
);
1159 case ColormapNotify
:
1160 client_update_colormap(client
, e
->xcolormap
.colormap
);
1165 if (extensions_shape
&& e
->type
== extensions_shape_event_basep
) {
1166 client
->shaped
= ((XShapeEvent
*)e
)->shaped
;
1167 frame_adjust_shape(client
->frame
);
1173 static void event_handle_dock(ObDock
*s
, XEvent
*e
)
1177 if (e
->xbutton
.button
== 1)
1178 stacking_raise(DOCK_AS_WINDOW(s
));
1179 else if (e
->xbutton
.button
== 2)
1180 stacking_lower(DOCK_AS_WINDOW(s
));
1191 static void event_handle_dockapp(ObDockApp
*app
, XEvent
*e
)
1195 dock_app_drag(app
, &e
->xmotion
);
1198 if (app
->ignore_unmaps
) {
1199 app
->ignore_unmaps
--;
1202 dock_remove(app
, TRUE
);
1205 dock_remove(app
, FALSE
);
1207 case ReparentNotify
:
1208 dock_remove(app
, FALSE
);
1210 case ConfigureNotify
:
1211 dock_app_configure(app
, e
->xconfigure
.width
, e
->xconfigure
.height
);
1216 static ObMenuFrame
* find_active_menu()
1219 ObMenuFrame
*ret
= NULL
;
1221 for (it
= menu_frame_visible
; it
; it
= g_list_next(it
)) {
1230 static ObMenuFrame
* find_active_or_last_menu()
1232 ObMenuFrame
*ret
= NULL
;
1234 ret
= find_active_menu();
1235 if (!ret
&& menu_frame_visible
)
1236 ret
= menu_frame_visible
->data
;
1240 static gboolean
event_handle_menu_keyboard(XEvent
*ev
)
1242 guint keycode
, state
;
1245 gboolean ret
= TRUE
;
1247 keycode
= ev
->xkey
.keycode
;
1248 state
= ev
->xkey
.state
;
1249 unikey
= translate_unichar(keycode
);
1251 frame
= find_active_or_last_menu();
1255 else if (keycode
== ob_keycode(OB_KEY_ESCAPE
) && state
== 0) {
1256 /* Escape closes the active menu */
1257 menu_frame_hide(frame
);
1260 else if (keycode
== ob_keycode(OB_KEY_RETURN
) && (state
== 0 ||
1261 state
== ControlMask
))
1263 /* Enter runs the active item or goes into the submenu.
1264 Control-Enter runs it without closing the menu. */
1266 menu_frame_select_next(frame
->child
);
1268 menu_entry_frame_execute(frame
->selected
, state
, ev
->xkey
.time
);
1271 else if (keycode
== ob_keycode(OB_KEY_LEFT
) && ev
->xkey
.state
== 0) {
1272 /* Left goes to the parent menu */
1273 menu_frame_select(frame
, NULL
, TRUE
);
1276 else if (keycode
== ob_keycode(OB_KEY_RIGHT
) && ev
->xkey
.state
== 0) {
1277 /* Right goes to the selected submenu */
1278 if (frame
->child
) menu_frame_select_next(frame
->child
);
1281 else if (keycode
== ob_keycode(OB_KEY_UP
) && state
== 0) {
1282 menu_frame_select_previous(frame
);
1285 else if (keycode
== ob_keycode(OB_KEY_DOWN
) && state
== 0) {
1286 menu_frame_select_next(frame
);
1289 /* keyboard accelerator shortcuts. */
1290 else if (ev
->xkey
.state
== 0 &&
1291 /* was it a valid key? */
1293 /* don't bother if the menu is empty. */
1298 ObMenuEntryFrame
*found
= NULL
;
1299 guint num_found
= 0;
1301 /* start after the selected one */
1302 start
= frame
->entries
;
1303 if (frame
->selected
) {
1304 for (it
= start
; frame
->selected
!= it
->data
; it
= g_list_next(it
))
1305 g_assert(it
!= NULL
); /* nothing was selected? */
1306 /* next with wraparound */
1307 start
= g_list_next(it
);
1308 if (start
== NULL
) start
= frame
->entries
;
1313 ObMenuEntryFrame
*e
= it
->data
;
1314 gunichar entrykey
= 0;
1316 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
)
1317 entrykey
= e
->entry
->data
.normal
.shortcut
;
1318 else if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
)
1319 entrykey
= e
->entry
->data
.submenu
.submenu
->shortcut
;
1321 if (unikey
== entrykey
) {
1322 if (found
== NULL
) found
= e
;
1326 /* next with wraparound */
1327 it
= g_list_next(it
);
1328 if (it
== NULL
) it
= frame
->entries
;
1329 } while (it
!= start
);
1332 if (found
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
1335 menu_frame_select(frame
, found
, TRUE
);
1336 usleep(50000); /* highlight the item for a short bit so the
1337 user can see what happened */
1338 menu_entry_frame_execute(found
, state
, ev
->xkey
.time
);
1340 menu_frame_select(frame
, found
, TRUE
);
1342 menu_frame_select_next(frame
->child
);
1353 static gboolean
event_handle_menu(XEvent
*ev
)
1356 ObMenuEntryFrame
*e
;
1357 gboolean ret
= TRUE
;
1361 if ((ev
->xbutton
.button
< 4 || ev
->xbutton
.button
> 5)
1364 if ((e
= menu_entry_frame_under(ev
->xbutton
.x_root
,
1365 ev
->xbutton
.y_root
)))
1366 menu_entry_frame_execute(e
, ev
->xbutton
.state
,
1369 menu_frame_hide_all();
1373 if ((e
= g_hash_table_lookup(menu_frame_map
, &ev
->xcrossing
.window
))) {
1374 if (e
->ignore_enters
)
1377 menu_frame_select(e
->frame
, e
, FALSE
);
1381 if ((e
= g_hash_table_lookup(menu_frame_map
, &ev
->xcrossing
.window
)) &&
1382 (f
= find_active_menu()) && f
->selected
== e
&&
1383 e
->entry
->type
!= OB_MENU_ENTRY_TYPE_SUBMENU
)
1385 menu_frame_select(e
->frame
, NULL
, FALSE
);
1388 if ((e
= menu_entry_frame_under(ev
->xmotion
.x_root
,
1389 ev
->xmotion
.y_root
)))
1390 menu_frame_select(e
->frame
, e
, FALSE
);
1393 ret
= event_handle_menu_keyboard(ev
);
1399 static void event_handle_user_input(ObClient
*client
, XEvent
*e
)
1401 g_assert(e
->type
== ButtonPress
|| e
->type
== ButtonRelease
||
1402 e
->type
== MotionNotify
|| e
->type
== KeyPress
||
1403 e
->type
== KeyRelease
);
1405 if (menu_frame_visible
) {
1406 if (event_handle_menu(e
))
1407 /* don't use the event if the menu used it, but if the menu
1408 didn't use it and it's a keypress that is bound, it will
1409 close the menu and be used */
1413 /* if the keyboard interactive action uses the event then dont
1414 use it for bindings. likewise is moveresize uses the event. */
1415 if (!keyboard_process_interactive_grab(e
, &client
) &&
1416 !(moveresize_in_progress
&& moveresize_event(e
)))
1418 if (moveresize_in_progress
)
1419 /* make further actions work on the client being
1421 client
= moveresize_client
;
1423 menu_can_hide
= FALSE
;
1424 ob_main_loop_timeout_add(ob_main_loop
,
1425 config_menu_hide_delay
* 1000,
1426 menu_hide_delay_func
,
1427 NULL
, g_direct_equal
, NULL
);
1429 if (e
->type
== ButtonPress
||
1430 e
->type
== ButtonRelease
||
1431 e
->type
== MotionNotify
)
1433 /* the frame may not be "visible" but they can still click on it
1434 in the case where it is animating before disappearing */
1435 if (!client
|| !frame_iconify_animating(client
->frame
))
1436 mouse_event(client
, e
);
1437 } else if (e
->type
== KeyPress
) {
1438 keyboard_event((focus_cycle_target
? focus_cycle_target
:
1439 (client
? client
: focus_client
)), e
);
1444 static gboolean
menu_hide_delay_func(gpointer data
)
1446 menu_can_hide
= TRUE
;
1447 return FALSE
; /* no repeat */
1450 static void focus_delay_dest(gpointer data
)
1455 static gboolean
focus_delay_cmp(gconstpointer d1
, gconstpointer d2
)
1457 const ObFocusDelayData
*f1
= d1
;
1458 return f1
->client
== d2
;
1461 static gboolean
focus_delay_func(gpointer data
)
1463 ObFocusDelayData
*d
= data
;
1464 Time old
= event_curtime
;
1466 event_curtime
= d
->time
;
1467 if (focus_client
!= d
->client
) {
1468 if (client_focus(d
->client
) && config_focus_raise
)
1469 client_raise(d
->client
);
1471 event_curtime
= old
;
1472 return FALSE
; /* no repeat */
1475 static void focus_delay_client_dest(ObClient
*client
, gpointer data
)
1477 ob_main_loop_timeout_remove_data(ob_main_loop
, focus_delay_func
,
1481 void event_halt_focus_delay()
1483 ob_main_loop_timeout_remove(ob_main_loop
, focus_delay_func
);
1486 void event_ignore_queued_enters()
1488 GSList
*saved
= NULL
, *it
;
1491 XSync(ob_display
, FALSE
);
1493 /* count the events */
1495 e
= g_new(XEvent
, 1);
1496 if (XCheckTypedEvent(ob_display
, EnterNotify
, e
)) {
1499 win
= g_hash_table_lookup(window_map
, &e
->xany
.window
);
1500 if (win
&& WINDOW_IS_CLIENT(win
))
1501 ++ignore_enter_focus
;
1503 saved
= g_slist_append(saved
, e
);
1509 /* put the events back */
1510 for (it
= saved
; it
; it
= g_slist_next(it
)) {
1511 XPutBackEvent(ob_display
, it
->data
);
1514 g_slist_free(saved
);
1517 gboolean
event_time_after(Time t1
, Time t2
)
1519 g_assert(t1
!= CurrentTime
);
1520 g_assert(t2
!= CurrentTime
);
1523 Timestamp values wrap around (after about 49.7 days). The server, given
1524 its current time is represented by timestamp T, always interprets
1525 timestamps from clients by treating half of the timestamp space as being
1526 later in time than T.
1527 - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1530 /* TIME_HALF is half of the number space of a Time type variable */
1531 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1533 if (t2
>= TIME_HALF
)
1534 /* t2 is in the second half so t1 might wrap around and be smaller than
1536 return t1
>= t2
|| t1
< (t2
+ TIME_HALF
);
1538 /* t2 is in the first half so t1 has to come after it */
1539 return t1
>= t2
&& t1
< (t2
+ TIME_HALF
);