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
);
87 static void focus_delay_dest(gpointer data
);
88 static gboolean
focus_delay_cmp(gconstpointer d1
, gconstpointer d2
);
89 static gboolean
focus_delay_func(gpointer data
);
90 static void focus_delay_client_dest(ObClient
*client
, gpointer data
);
92 static gboolean
menu_hide_delay_func(gpointer data
);
94 /* The time for the current event being processed */
95 Time event_curtime
= CurrentTime
;
97 static guint ignore_enter_focus
= 0;
98 static gboolean menu_can_hide
;
101 static void ice_handler(gint fd
, gpointer conn
)
104 IceProcessMessages(conn
, NULL
, &b
);
107 static void ice_watch(IceConn conn
, IcePointer data
, Bool opening
,
108 IcePointer
*watch_data
)
113 fd
= IceConnectionNumber(conn
);
114 ob_main_loop_fd_add(ob_main_loop
, fd
, ice_handler
, conn
, NULL
);
116 ob_main_loop_fd_remove(ob_main_loop
, fd
);
122 void event_startup(gboolean reconfig
)
124 if (reconfig
) return;
126 ob_main_loop_x_add(ob_main_loop
, event_process
, NULL
, NULL
);
129 IceAddConnectionWatch(ice_watch
, NULL
);
132 client_add_destructor(focus_delay_client_dest
, NULL
);
135 void event_shutdown(gboolean reconfig
)
137 if (reconfig
) return;
140 IceRemoveConnectionWatch(ice_watch
, NULL
);
143 client_remove_destructor(focus_delay_client_dest
);
146 static Window
event_get_window(XEvent
*e
)
153 window
= RootWindow(ob_display
, ob_screen
);
156 window
= e
->xmap
.window
;
159 window
= e
->xunmap
.window
;
162 window
= e
->xdestroywindow
.window
;
164 case ConfigureRequest
:
165 window
= e
->xconfigurerequest
.window
;
167 case ConfigureNotify
:
168 window
= e
->xconfigure
.window
;
172 if (extensions_xkb
&& e
->type
== extensions_xkb_event_basep
) {
173 switch (((XkbAnyEvent
*)e
)->xkb_type
) {
175 window
= ((XkbBellNotifyEvent
*)e
)->window
;
182 if (extensions_sync
&&
183 e
->type
== extensions_sync_event_basep
+ XSyncAlarmNotify
)
188 window
= e
->xany
.window
;
193 static void event_set_curtime(XEvent
*e
)
195 Time t
= CurrentTime
;
197 /* grab the lasttime and hack up the state */
213 t
= e
->xproperty
.time
;
217 t
= e
->xcrossing
.time
;
221 if (extensions_sync
&&
222 e
->type
== extensions_sync_event_basep
+ XSyncAlarmNotify
)
224 t
= ((XSyncAlarmNotifyEvent
*)e
)->time
;
227 /* if more event types are anticipated, get their timestamp
235 static void event_hack_mods(XEvent
*e
)
238 XkbStateRec xkb_state
;
244 e
->xbutton
.state
= modkeys_only_modifier_masks(e
->xbutton
.state
);
247 e
->xkey
.state
= modkeys_only_modifier_masks(e
->xkey
.state
);
250 e
->xkey
.state
= modkeys_only_modifier_masks(e
->xkey
.state
);
252 if (XkbGetState(ob_display
, XkbUseCoreKbd
, &xkb_state
) == Success
) {
253 e
->xkey
.state
= xkb_state
.compat_state
;
257 /* remove from the state the mask of the modifier key being released,
258 if it is a modifier key being released that is */
259 e
->xkey
.state
&= ~modkeys_keycode_to_mask(e
->xkey
.keycode
);
262 e
->xmotion
.state
= modkeys_only_modifier_masks(e
->xmotion
.state
);
263 /* compress events */
266 while (XCheckTypedWindowEvent(ob_display
, e
->xmotion
.window
,
268 e
->xmotion
.x_root
= ce
.xmotion
.x_root
;
269 e
->xmotion
.y_root
= ce
.xmotion
.y_root
;
276 static gboolean
wanted_focusevent(XEvent
*e
)
278 gint mode
= e
->xfocus
.mode
;
279 gint detail
= e
->xfocus
.detail
;
280 Window win
= e
->xany
.window
;
282 if (e
->type
== FocusIn
) {
284 /* These are ones we never want.. */
286 /* This means focus was given by a keyboard/mouse grab. */
287 if (mode
== NotifyGrab
)
289 /* This means focus was given back from a keyboard/mouse grab. */
290 if (mode
== NotifyUngrab
)
293 /* These are the ones we want.. */
295 if (win
== RootWindow(ob_display
, ob_screen
)) {
296 /* This means focus reverted off of a client */
297 if (detail
== NotifyPointerRoot
|| detail
== NotifyDetailNone
||
298 detail
== NotifyInferior
)
304 /* This means focus moved from the root window to a client */
305 if (detail
== NotifyVirtual
)
307 /* This means focus moved from one client to another */
308 if (detail
== NotifyNonlinearVirtual
)
314 g_assert(e
->type
== FocusOut
);
317 /* These are ones we never want.. */
319 /* This means focus was taken by a keyboard/mouse grab. */
320 if (mode
== NotifyGrab
)
323 /* Focus left the root window revertedto state */
324 if (win
== RootWindow(ob_display
, ob_screen
))
327 /* These are the ones we want.. */
329 /* This means focus moved from a client to the root window */
330 if (detail
== NotifyVirtual
)
332 /* This means focus moved from one client to another */
333 if (detail
== NotifyNonlinearVirtual
)
335 /* This means focus had moved to our frame window and now moved off */
336 if (detail
== NotifyNonlinear
)
344 static Bool
look_for_focusin(Display
*d
, XEvent
*e
, XPointer arg
)
346 return e
->type
== FocusIn
&& wanted_focusevent(e
);
349 static gboolean
event_ignore(XEvent
*e
, ObClient
*client
)
353 if (!wanted_focusevent(e
))
357 if (!wanted_focusevent(e
))
364 static void event_process(const XEvent
*ec
, gpointer data
)
367 ObGroup
*group
= NULL
;
368 ObClient
*client
= NULL
;
370 ObDockApp
*dockapp
= NULL
;
371 ObWindow
*obwin
= NULL
;
373 ObEventData
*ed
= data
;
375 /* make a copy we can mangle */
379 window
= event_get_window(e
);
380 if (!(e
->type
== PropertyNotify
&&
381 (group
= g_hash_table_lookup(group_map
, &window
))))
382 if ((obwin
= g_hash_table_lookup(window_map
, &window
))) {
383 switch (obwin
->type
) {
385 dock
= WINDOW_AS_DOCK(obwin
);
388 dockapp
= WINDOW_AS_DOCKAPP(obwin
);
391 client
= WINDOW_AS_CLIENT(obwin
);
394 case Window_Internal
:
395 /* not to be used for events */
396 g_assert_not_reached();
401 event_set_curtime(e
);
403 if (event_ignore(e
, client
)) {
410 /* deal with it in the kernel */
412 if (menu_frame_visible
&&
413 (e
->type
== EnterNotify
|| e
->type
== LeaveNotify
))
415 /* crossing events for menu */
416 event_handle_menu(e
);
417 } else if (e
->type
== FocusIn
) {
418 if (client
&& client
!= focus_client
) {
419 frame_adjust_focus(client
->frame
, TRUE
);
420 focus_set_client(client
);
421 client_calc_layer(client
);
423 } else if (e
->type
== FocusOut
) {
424 gboolean nomove
= FALSE
;
427 ob_debug_type(OB_DEBUG_FOCUS
, "FocusOut Event\n");
429 /* Look for the followup FocusIn */
430 if (!XCheckIfEvent(ob_display
, &ce
, look_for_focusin
, NULL
)) {
431 /* There is no FocusIn, this means focus went to a window that
432 is not being managed, or a window on another screen. */
433 ob_debug_type(OB_DEBUG_FOCUS
, "Focus went to a black hole !\n");
434 /* nothing is focused */
435 focus_set_client(NULL
);
436 } else if (ce
.xany
.window
== e
->xany
.window
) {
437 ob_debug_type(OB_DEBUG_FOCUS
, "Focus didn't go anywhere\n");
438 /* If focus didn't actually move anywhere, there is nothing to do*/
440 } else if (ce
.xfocus
.detail
== NotifyPointerRoot
||
441 ce
.xfocus
.detail
== NotifyDetailNone
||
442 ce
.xfocus
.detail
== NotifyInferior
) {
443 ob_debug_type(OB_DEBUG_FOCUS
, "Focus went to root\n");
444 /* Focus has been reverted to the root window or nothing
445 FocusOut events come after UnmapNotify, so we don't need to
446 worry about focusing an invalid window
448 focus_fallback(TRUE
);
450 /* Focus did move, so process the FocusIn event */
451 ObEventData ed
= { .ignored
= FALSE
};
452 event_process(&ce
, &ed
);
454 /* The FocusIn was ignored, this means it was on a window
455 that isn't a client. */
456 ob_debug_type(OB_DEBUG_FOCUS
,
457 "Focus went to an unmanaged window 0x%x !\n",
459 focus_fallback(TRUE
);
463 if (client
&& !nomove
) {
464 frame_adjust_focus(client
->frame
, FALSE
);
465 /* focus_set_client has already been called for sure */
466 client_calc_layer(client
);
469 event_handle_group(group
, e
);
471 event_handle_client(client
, e
);
473 event_handle_dockapp(dockapp
, e
);
475 event_handle_dock(dock
, e
);
476 else if (window
== RootWindow(ob_display
, ob_screen
))
477 event_handle_root(e
);
478 else if (e
->type
== MapRequest
)
479 client_manage(window
);
480 else if (e
->type
== ConfigureRequest
) {
481 /* unhandled configure requests must be used to configure the
485 xwc
.x
= e
->xconfigurerequest
.x
;
486 xwc
.y
= e
->xconfigurerequest
.y
;
487 xwc
.width
= e
->xconfigurerequest
.width
;
488 xwc
.height
= e
->xconfigurerequest
.height
;
489 xwc
.border_width
= e
->xconfigurerequest
.border_width
;
490 xwc
.sibling
= e
->xconfigurerequest
.above
;
491 xwc
.stack_mode
= e
->xconfigurerequest
.detail
;
493 /* we are not to be held responsible if someone sends us an
495 xerror_set_ignore(TRUE
);
496 XConfigureWindow(ob_display
, window
,
497 e
->xconfigurerequest
.value_mask
, &xwc
);
498 xerror_set_ignore(FALSE
);
501 else if (extensions_sync
&&
502 e
->type
== extensions_sync_event_basep
+ XSyncAlarmNotify
)
504 XSyncAlarmNotifyEvent
*se
= (XSyncAlarmNotifyEvent
*)e
;
505 if (se
->alarm
== moveresize_alarm
&& moveresize_in_progress
)
510 /* user input (action-bound) events */
511 if (e
->type
== ButtonPress
|| e
->type
== ButtonRelease
||
512 e
->type
== MotionNotify
|| e
->type
== KeyPress
||
513 e
->type
== KeyRelease
)
515 gboolean useevent
= TRUE
;
517 if (menu_frame_visible
) {
518 if (event_handle_menu(e
))
519 /* don't use the event if the menu used it, but if the menu
520 didn't use it and it's a keypress that is bound, it will
521 close the menu and be used */
526 if (!keyboard_process_interactive_grab(e
, &client
)) {
527 if (moveresize_in_progress
) {
530 /* make further actions work on the client being
532 client
= moveresize_client
;
535 menu_can_hide
= FALSE
;
536 ob_main_loop_timeout_add(ob_main_loop
,
537 config_menu_hide_delay
* 1000,
538 menu_hide_delay_func
,
539 NULL
, g_direct_equal
, NULL
);
541 if (e
->type
== ButtonPress
|| e
->type
== ButtonRelease
||
542 e
->type
== MotionNotify
) {
543 mouse_event(client
, e
);
544 } else if (e
->type
== KeyPress
) {
545 keyboard_event((focus_cycle_target
? focus_cycle_target
:
546 (client
? client
: focus_client
)), e
);
551 /* if something happens and it's not from an XEvent, then we don't know
553 event_curtime
= CurrentTime
;
556 static void event_handle_root(XEvent
*e
)
562 ob_debug("Another WM has requested to replace us. Exiting.\n");
567 if (e
->xclient
.format
!= 32) break;
569 msgtype
= e
->xclient
.message_type
;
570 if (msgtype
== prop_atoms
.net_current_desktop
) {
571 guint d
= e
->xclient
.data
.l
[0];
572 if (d
< screen_num_desktops
) {
573 event_curtime
= e
->xclient
.data
.l
[1];
574 ob_debug("SWITCH DESKTOP TIME: %d\n", event_curtime
);
575 screen_set_desktop(d
);
577 } else if (msgtype
== prop_atoms
.net_number_of_desktops
) {
578 guint d
= e
->xclient
.data
.l
[0];
580 screen_set_num_desktops(d
);
581 } else if (msgtype
== prop_atoms
.net_showing_desktop
) {
582 screen_show_desktop(e
->xclient
.data
.l
[0] != 0);
583 } else if (msgtype
== prop_atoms
.ob_control
) {
584 if (e
->xclient
.data
.l
[0] == 1)
586 else if (e
->xclient
.data
.l
[0] == 2)
591 if (e
->xproperty
.atom
== prop_atoms
.net_desktop_names
)
592 screen_update_desktop_names();
593 else if (e
->xproperty
.atom
== prop_atoms
.net_desktop_layout
)
594 screen_update_layout();
596 case ConfigureNotify
:
598 XRRUpdateConfiguration(e
);
607 static void event_handle_group(ObGroup
*group
, XEvent
*e
)
611 g_assert(e
->type
== PropertyNotify
);
613 for (it
= group
->members
; it
; it
= g_slist_next(it
))
614 event_handle_client(it
->data
, e
);
617 void event_enter_client(ObClient
*client
)
619 g_assert(config_focus_follow
);
621 if (client_normal(client
) && client_can_focus(client
)) {
622 if (config_focus_delay
) {
623 ObFocusDelayData
*data
;
625 ob_main_loop_timeout_remove(ob_main_loop
, focus_delay_func
);
627 data
= g_new(ObFocusDelayData
, 1);
628 data
->client
= client
;
629 data
->time
= event_curtime
;
631 ob_main_loop_timeout_add(ob_main_loop
,
634 data
, focus_delay_cmp
, focus_delay_dest
);
636 ObFocusDelayData data
;
637 data
.client
= client
;
638 data
.time
= event_curtime
;
639 focus_delay_func(&data
);
644 static void event_handle_client(ObClient
*client
, XEvent
*e
)
654 /* Wheel buttons don't draw because they are an instant click, so it
655 is a waste of resources to go drawing it. */
656 if (!(e
->xbutton
.button
== 4 || e
->xbutton
.button
== 5)) {
657 con
= frame_context(client
, e
->xbutton
.window
);
658 con
= mouse_button_frame_context(con
, e
->xbutton
.button
);
660 case OB_FRAME_CONTEXT_MAXIMIZE
:
661 client
->frame
->max_press
= (e
->type
== ButtonPress
);
662 framerender_frame(client
->frame
);
664 case OB_FRAME_CONTEXT_CLOSE
:
665 client
->frame
->close_press
= (e
->type
== ButtonPress
);
666 framerender_frame(client
->frame
);
668 case OB_FRAME_CONTEXT_ICONIFY
:
669 client
->frame
->iconify_press
= (e
->type
== ButtonPress
);
670 framerender_frame(client
->frame
);
672 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
673 client
->frame
->desk_press
= (e
->type
== ButtonPress
);
674 framerender_frame(client
->frame
);
676 case OB_FRAME_CONTEXT_SHADE
:
677 client
->frame
->shade_press
= (e
->type
== ButtonPress
);
678 framerender_frame(client
->frame
);
681 /* nothing changes with clicks for any other contexts */
687 con
= frame_context(client
, e
->xcrossing
.window
);
689 case OB_FRAME_CONTEXT_MAXIMIZE
:
690 client
->frame
->max_hover
= FALSE
;
691 frame_adjust_state(client
->frame
);
693 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
694 client
->frame
->desk_hover
= FALSE
;
695 frame_adjust_state(client
->frame
);
697 case OB_FRAME_CONTEXT_SHADE
:
698 client
->frame
->shade_hover
= FALSE
;
699 frame_adjust_state(client
->frame
);
701 case OB_FRAME_CONTEXT_ICONIFY
:
702 client
->frame
->iconify_hover
= FALSE
;
703 frame_adjust_state(client
->frame
);
705 case OB_FRAME_CONTEXT_CLOSE
:
706 client
->frame
->close_hover
= FALSE
;
707 frame_adjust_state(client
->frame
);
709 case OB_FRAME_CONTEXT_FRAME
:
710 ob_debug_type(OB_DEBUG_FOCUS
,
711 "%sNotify mode %d detail %d on %lx\n",
712 (e
->type
== EnterNotify
? "Enter" : "Leave"),
714 e
->xcrossing
.detail
, (client
?client
->window
:0));
715 if (keyboard_interactively_grabbed())
717 if (config_focus_follow
&& config_focus_delay
&&
718 /* leave inferior events can happen when the mouse goes onto
719 the window's border and then into the window before the
721 e
->xcrossing
.detail
!= NotifyInferior
)
723 ob_main_loop_timeout_remove_data(ob_main_loop
,
734 gboolean nofocus
= FALSE
;
736 if (ignore_enter_focus
) {
737 ignore_enter_focus
--;
741 con
= frame_context(client
, e
->xcrossing
.window
);
743 case OB_FRAME_CONTEXT_MAXIMIZE
:
744 client
->frame
->max_hover
= TRUE
;
745 frame_adjust_state(client
->frame
);
747 case OB_FRAME_CONTEXT_ALLDESKTOPS
:
748 client
->frame
->desk_hover
= TRUE
;
749 frame_adjust_state(client
->frame
);
751 case OB_FRAME_CONTEXT_SHADE
:
752 client
->frame
->shade_hover
= TRUE
;
753 frame_adjust_state(client
->frame
);
755 case OB_FRAME_CONTEXT_ICONIFY
:
756 client
->frame
->iconify_hover
= TRUE
;
757 frame_adjust_state(client
->frame
);
759 case OB_FRAME_CONTEXT_CLOSE
:
760 client
->frame
->close_hover
= TRUE
;
761 frame_adjust_state(client
->frame
);
763 case OB_FRAME_CONTEXT_FRAME
:
764 if (keyboard_interactively_grabbed())
766 if (e
->xcrossing
.mode
== NotifyGrab
||
767 e
->xcrossing
.mode
== NotifyUngrab
||
768 /*ignore enters when we're already in the window */
769 e
->xcrossing
.detail
== NotifyInferior
)
771 ob_debug_type(OB_DEBUG_FOCUS
,
772 "%sNotify mode %d detail %d on %lx IGNORED\n",
773 (e
->type
== EnterNotify
? "Enter" : "Leave"),
775 e
->xcrossing
.detail
, client
?client
->window
:0);
777 ob_debug_type(OB_DEBUG_FOCUS
,
778 "%sNotify mode %d detail %d on %lx, "
779 "focusing window: %d\n",
780 (e
->type
== EnterNotify
? "Enter" : "Leave"),
782 e
->xcrossing
.detail
, (client
?client
->window
:0),
784 if (!nofocus
&& config_focus_follow
)
785 event_enter_client(client
);
793 case ConfigureRequest
:
795 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
796 ConfigureRequest
, &ce
)) {
798 /* XXX if this causes bad things.. we can compress config req's
799 with the same mask. */
800 e
->xconfigurerequest
.value_mask
|=
801 ce
.xconfigurerequest
.value_mask
;
802 if (ce
.xconfigurerequest
.value_mask
& CWX
)
803 e
->xconfigurerequest
.x
= ce
.xconfigurerequest
.x
;
804 if (ce
.xconfigurerequest
.value_mask
& CWY
)
805 e
->xconfigurerequest
.y
= ce
.xconfigurerequest
.y
;
806 if (ce
.xconfigurerequest
.value_mask
& CWWidth
)
807 e
->xconfigurerequest
.width
= ce
.xconfigurerequest
.width
;
808 if (ce
.xconfigurerequest
.value_mask
& CWHeight
)
809 e
->xconfigurerequest
.height
= ce
.xconfigurerequest
.height
;
810 if (ce
.xconfigurerequest
.value_mask
& CWBorderWidth
)
811 e
->xconfigurerequest
.border_width
=
812 ce
.xconfigurerequest
.border_width
;
813 if (ce
.xconfigurerequest
.value_mask
& CWStackMode
)
814 e
->xconfigurerequest
.detail
= ce
.xconfigurerequest
.detail
;
817 /* if we are iconic (or shaded (fvwm does this)) ignore the event */
818 if (client
->iconic
|| client
->shaded
) return;
820 /* resize, then move, as specified in the EWMH section 7.7 */
821 if (e
->xconfigurerequest
.value_mask
& (CWWidth
| CWHeight
|
826 if (e
->xconfigurerequest
.value_mask
& CWBorderWidth
)
827 client
->border_width
= e
->xconfigurerequest
.border_width
;
829 x
= (e
->xconfigurerequest
.value_mask
& CWX
) ?
830 e
->xconfigurerequest
.x
: client
->area
.x
;
831 y
= (e
->xconfigurerequest
.value_mask
& CWY
) ?
832 e
->xconfigurerequest
.y
: client
->area
.y
;
833 w
= (e
->xconfigurerequest
.value_mask
& CWWidth
) ?
834 e
->xconfigurerequest
.width
: client
->area
.width
;
835 h
= (e
->xconfigurerequest
.value_mask
& CWHeight
) ?
836 e
->xconfigurerequest
.height
: client
->area
.height
;
838 ob_debug("ConfigureRequest x %d %d y %d %d\n",
839 e
->xconfigurerequest
.value_mask
& CWX
, x
,
840 e
->xconfigurerequest
.value_mask
& CWY
, y
);
842 client_find_onscreen(client
, &x
, &y
, w
, h
, FALSE
);
843 client_configure_full(client
, x
, y
, w
, h
, FALSE
, TRUE
, TRUE
);
846 if (e
->xconfigurerequest
.value_mask
& CWStackMode
) {
847 switch (e
->xconfigurerequest
.detail
) {
850 /* Apps are so rude. And this is totally disconnected from
851 activation/focus. Bleh. */
852 /*client_lower(client);*/
858 /* Apps are so rude. And this is totally disconnected from
859 activation/focus. Bleh. */
860 /*client_raise(client);*/
866 if (client
->ignore_unmaps
) {
867 client
->ignore_unmaps
--;
870 ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
872 client
->window
, e
->xunmap
.event
, e
->xunmap
.from_configure
,
873 client
->ignore_unmaps
);
874 client_unmanage(client
);
877 ob_debug("DestroyNotify for window 0x%x\n", client
->window
);
878 client_unmanage(client
);
881 /* this is when the client is first taken captive in the frame */
882 if (e
->xreparent
.parent
== client
->frame
->plate
) break;
885 This event is quite rare and is usually handled in unmapHandler.
886 However, if the window is unmapped when the reparent event occurs,
887 the window manager never sees it because an unmap event is not sent
888 to an already unmapped window.
891 /* we don't want the reparent event, put it back on the stack for the
892 X server to deal with after we unmanage the window */
893 XPutBackEvent(ob_display
, e
);
895 ob_debug("ReparentNotify for window 0x%x\n", client
->window
);
896 client_unmanage(client
);
899 ob_debug("MapRequest for 0x%lx\n", client
->window
);
900 if (!client
->iconic
) break; /* this normally doesn't happen, but if it
901 does, we don't want it!
902 it can happen now when the window is on
903 another desktop, but we still don't
905 client_activate(client
, FALSE
, TRUE
);
908 /* validate cuz we query stuff off the client here */
909 if (!client_validate(client
)) break;
911 if (e
->xclient
.format
!= 32) return;
913 msgtype
= e
->xclient
.message_type
;
914 if (msgtype
== prop_atoms
.wm_change_state
) {
915 /* compress changes into a single change */
916 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
918 /* XXX: it would be nice to compress ALL messages of a
919 type, not just messages in a row without other
920 message types between. */
921 if (ce
.xclient
.message_type
!= msgtype
) {
922 XPutBackEvent(ob_display
, &ce
);
925 e
->xclient
= ce
.xclient
;
927 client_set_wm_state(client
, e
->xclient
.data
.l
[0]);
928 } else if (msgtype
== prop_atoms
.net_wm_desktop
) {
929 /* compress changes into a single change */
930 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
932 /* XXX: it would be nice to compress ALL messages of a
933 type, not just messages in a row without other
934 message types between. */
935 if (ce
.xclient
.message_type
!= msgtype
) {
936 XPutBackEvent(ob_display
, &ce
);
939 e
->xclient
= ce
.xclient
;
941 if ((unsigned)e
->xclient
.data
.l
[0] < screen_num_desktops
||
942 (unsigned)e
->xclient
.data
.l
[0] == DESKTOP_ALL
)
943 client_set_desktop(client
, (unsigned)e
->xclient
.data
.l
[0],
945 } else if (msgtype
== prop_atoms
.net_wm_state
) {
946 /* can't compress these */
947 ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
948 (e
->xclient
.data
.l
[0] == 0 ? "Remove" :
949 e
->xclient
.data
.l
[0] == 1 ? "Add" :
950 e
->xclient
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
951 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2],
953 client_set_state(client
, e
->xclient
.data
.l
[0],
954 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2]);
955 } else if (msgtype
== prop_atoms
.net_close_window
) {
956 ob_debug("net_close_window for 0x%lx\n", client
->window
);
957 client_close(client
);
958 } else if (msgtype
== prop_atoms
.net_active_window
) {
959 ob_debug("net_active_window for 0x%lx source=%s\n",
961 (e
->xclient
.data
.l
[0] == 0 ? "unknown" :
962 (e
->xclient
.data
.l
[0] == 1 ? "application" :
963 (e
->xclient
.data
.l
[0] == 2 ? "user" : "INVALID"))));
964 /* XXX make use of data.l[2] ! */
965 event_curtime
= e
->xclient
.data
.l
[1];
966 client_activate(client
, FALSE
,
967 (e
->xclient
.data
.l
[0] == 0 ||
968 e
->xclient
.data
.l
[0] == 2));
969 } else if (msgtype
== prop_atoms
.net_wm_moveresize
) {
970 ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
971 client
->window
, e
->xclient
.data
.l
[2]);
972 if ((Atom
)e
->xclient
.data
.l
[2] ==
973 prop_atoms
.net_wm_moveresize_size_topleft
||
974 (Atom
)e
->xclient
.data
.l
[2] ==
975 prop_atoms
.net_wm_moveresize_size_top
||
976 (Atom
)e
->xclient
.data
.l
[2] ==
977 prop_atoms
.net_wm_moveresize_size_topright
||
978 (Atom
)e
->xclient
.data
.l
[2] ==
979 prop_atoms
.net_wm_moveresize_size_right
||
980 (Atom
)e
->xclient
.data
.l
[2] ==
981 prop_atoms
.net_wm_moveresize_size_right
||
982 (Atom
)e
->xclient
.data
.l
[2] ==
983 prop_atoms
.net_wm_moveresize_size_bottomright
||
984 (Atom
)e
->xclient
.data
.l
[2] ==
985 prop_atoms
.net_wm_moveresize_size_bottom
||
986 (Atom
)e
->xclient
.data
.l
[2] ==
987 prop_atoms
.net_wm_moveresize_size_bottomleft
||
988 (Atom
)e
->xclient
.data
.l
[2] ==
989 prop_atoms
.net_wm_moveresize_size_left
||
990 (Atom
)e
->xclient
.data
.l
[2] ==
991 prop_atoms
.net_wm_moveresize_move
||
992 (Atom
)e
->xclient
.data
.l
[2] ==
993 prop_atoms
.net_wm_moveresize_size_keyboard
||
994 (Atom
)e
->xclient
.data
.l
[2] ==
995 prop_atoms
.net_wm_moveresize_move_keyboard
) {
997 moveresize_start(client
, e
->xclient
.data
.l
[0],
998 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[3],
999 e
->xclient
.data
.l
[2]);
1001 else if ((Atom
)e
->xclient
.data
.l
[2] ==
1002 prop_atoms
.net_wm_moveresize_cancel
)
1003 moveresize_end(TRUE
);
1004 } else if (msgtype
== prop_atoms
.net_moveresize_window
) {
1005 gint grav
, x
, y
, w
, h
;
1007 if (e
->xclient
.data
.l
[0] & 0xff)
1008 grav
= e
->xclient
.data
.l
[0] & 0xff;
1010 grav
= client
->gravity
;
1012 if (e
->xclient
.data
.l
[0] & 1 << 8)
1013 x
= e
->xclient
.data
.l
[1];
1016 if (e
->xclient
.data
.l
[0] & 1 << 9)
1017 y
= e
->xclient
.data
.l
[2];
1020 if (e
->xclient
.data
.l
[0] & 1 << 10)
1021 w
= e
->xclient
.data
.l
[3];
1023 w
= client
->area
.width
;
1024 if (e
->xclient
.data
.l
[0] & 1 << 11)
1025 h
= e
->xclient
.data
.l
[4];
1027 h
= client
->area
.height
;
1029 ob_debug("MOVERESIZE x %d %d y %d %d\n",
1030 e
->xclient
.data
.l
[0] & 1 << 8, x
,
1031 e
->xclient
.data
.l
[0] & 1 << 9, y
);
1032 client_convert_gravity(client
, grav
, &x
, &y
, w
, h
);
1033 client_find_onscreen(client
, &x
, &y
, w
, h
, FALSE
);
1034 client_configure(client
, x
, y
, w
, h
, FALSE
, TRUE
);
1037 case PropertyNotify
:
1038 /* validate cuz we query stuff off the client here */
1039 if (!client_validate(client
)) break;
1041 /* compress changes to a single property into a single change */
1042 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
1046 /* XXX: it would be nice to compress ALL changes to a property,
1047 not just changes in a row without other props between. */
1049 a
= ce
.xproperty
.atom
;
1050 b
= e
->xproperty
.atom
;
1054 if ((a
== prop_atoms
.net_wm_name
||
1055 a
== prop_atoms
.wm_name
||
1056 a
== prop_atoms
.net_wm_icon_name
||
1057 a
== prop_atoms
.wm_icon_name
)
1059 (b
== prop_atoms
.net_wm_name
||
1060 b
== prop_atoms
.wm_name
||
1061 b
== prop_atoms
.net_wm_icon_name
||
1062 b
== prop_atoms
.wm_icon_name
)) {
1065 if (a
== prop_atoms
.net_wm_icon
&&
1066 b
== prop_atoms
.net_wm_icon
)
1069 XPutBackEvent(ob_display
, &ce
);
1073 msgtype
= e
->xproperty
.atom
;
1074 if (msgtype
== XA_WM_NORMAL_HINTS
) {
1075 client_update_normal_hints(client
);
1076 /* normal hints can make a window non-resizable */
1077 client_setup_decor_and_functions(client
);
1078 } else if (msgtype
== XA_WM_HINTS
) {
1079 client_update_wmhints(client
);
1080 } else if (msgtype
== XA_WM_TRANSIENT_FOR
) {
1081 client_update_transient_for(client
);
1082 client_get_type(client
);
1083 /* type may have changed, so update the layer */
1084 client_calc_layer(client
);
1085 client_setup_decor_and_functions(client
);
1086 } else if (msgtype
== prop_atoms
.net_wm_name
||
1087 msgtype
== prop_atoms
.wm_name
||
1088 msgtype
== prop_atoms
.net_wm_icon_name
||
1089 msgtype
== prop_atoms
.wm_icon_name
) {
1090 client_update_title(client
);
1091 } else if (msgtype
== prop_atoms
.wm_class
) {
1092 client_update_class(client
);
1093 } else if (msgtype
== prop_atoms
.wm_protocols
) {
1094 client_update_protocols(client
);
1095 client_setup_decor_and_functions(client
);
1097 else if (msgtype
== prop_atoms
.net_wm_strut
) {
1098 client_update_strut(client
);
1100 else if (msgtype
== prop_atoms
.net_wm_icon
) {
1101 client_update_icons(client
);
1103 else if (msgtype
== prop_atoms
.net_wm_user_time
) {
1104 client_update_user_time(client
);
1107 else if (msgtype
== prop_atoms
.net_wm_sync_request_counter
) {
1108 client_update_sync_request_counter(client
);
1111 else if (msgtype
== prop_atoms
.sm_client_id
) {
1112 client_update_sm_client_id(client
);
1114 case ColormapNotify
:
1115 client_update_colormap(client
, e
->xcolormap
.colormap
);
1120 if (extensions_shape
&& e
->type
== extensions_shape_event_basep
) {
1121 client
->shaped
= ((XShapeEvent
*)e
)->shaped
;
1122 frame_adjust_shape(client
->frame
);
1128 static void event_handle_dock(ObDock
*s
, XEvent
*e
)
1132 if (e
->xbutton
.button
== 1)
1133 stacking_raise(DOCK_AS_WINDOW(s
));
1134 else if (e
->xbutton
.button
== 2)
1135 stacking_lower(DOCK_AS_WINDOW(s
));
1146 static void event_handle_dockapp(ObDockApp
*app
, XEvent
*e
)
1150 dock_app_drag(app
, &e
->xmotion
);
1153 if (app
->ignore_unmaps
) {
1154 app
->ignore_unmaps
--;
1157 dock_remove(app
, TRUE
);
1160 dock_remove(app
, FALSE
);
1162 case ReparentNotify
:
1163 dock_remove(app
, FALSE
);
1165 case ConfigureNotify
:
1166 dock_app_configure(app
, e
->xconfigure
.width
, e
->xconfigure
.height
);
1171 static ObMenuFrame
* find_active_menu()
1174 ObMenuFrame
*ret
= NULL
;
1176 for (it
= menu_frame_visible
; it
; it
= g_list_next(it
)) {
1185 static ObMenuFrame
* find_active_or_last_menu()
1187 ObMenuFrame
*ret
= NULL
;
1189 ret
= find_active_menu();
1190 if (!ret
&& menu_frame_visible
)
1191 ret
= menu_frame_visible
->data
;
1195 static gboolean
event_handle_menu_keyboard(XEvent
*ev
)
1197 guint keycode
, state
;
1200 gboolean ret
= TRUE
;
1202 keycode
= ev
->xkey
.keycode
;
1203 state
= ev
->xkey
.state
;
1204 unikey
= translate_unichar(keycode
);
1206 frame
= find_active_or_last_menu();
1210 else if (keycode
== ob_keycode(OB_KEY_ESCAPE
) && state
== 0) {
1211 /* Escape closes the active menu */
1212 menu_frame_hide(frame
);
1215 else if (keycode
== ob_keycode(OB_KEY_RETURN
) && (state
== 0 ||
1216 state
== ControlMask
))
1218 /* Enter runs the active item or goes into the submenu.
1219 Control-Enter runs it without closing the menu. */
1221 menu_frame_select_next(frame
->child
);
1223 menu_entry_frame_execute(frame
->selected
, state
, ev
->xkey
.time
);
1226 else if (keycode
== ob_keycode(OB_KEY_LEFT
) && ev
->xkey
.state
== 0) {
1227 /* Left goes to the parent menu */
1228 menu_frame_select(frame
, NULL
, TRUE
);
1231 else if (keycode
== ob_keycode(OB_KEY_RIGHT
) && ev
->xkey
.state
== 0) {
1232 /* Right goes to the selected submenu */
1233 if (frame
->child
) menu_frame_select_next(frame
->child
);
1236 else if (keycode
== ob_keycode(OB_KEY_UP
) && state
== 0) {
1237 menu_frame_select_previous(frame
);
1240 else if (keycode
== ob_keycode(OB_KEY_DOWN
) && state
== 0) {
1241 menu_frame_select_next(frame
);
1244 /* keyboard accelerator shortcuts. */
1245 else if (ev
->xkey
.state
== 0 &&
1246 /* was it a valid key? */
1248 /* don't bother if the menu is empty. */
1253 ObMenuEntryFrame
*found
= NULL
;
1254 guint num_found
= 0;
1256 /* start after the selected one */
1257 start
= frame
->entries
;
1258 if (frame
->selected
) {
1259 for (it
= start
; frame
->selected
!= it
->data
; it
= g_list_next(it
))
1260 g_assert(it
!= NULL
); /* nothing was selected? */
1261 /* next with wraparound */
1262 start
= g_list_next(it
);
1263 if (start
== NULL
) start
= frame
->entries
;
1268 ObMenuEntryFrame
*e
= it
->data
;
1269 gunichar entrykey
= 0;
1271 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
1272 e
->entry
->data
.normal
.enabled
)
1273 entrykey
= e
->entry
->data
.normal
.shortcut
;
1274 else if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
)
1275 entrykey
= e
->entry
->data
.submenu
.submenu
->shortcut
;
1277 if (unikey
== entrykey
) {
1278 if (found
== NULL
) found
= e
;
1282 /* next with wraparound */
1283 it
= g_list_next(it
);
1284 if (it
== NULL
) it
= frame
->entries
;
1285 } while (it
!= start
);
1288 if (found
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
1291 menu_frame_select(frame
, found
, TRUE
);
1293 menu_entry_frame_execute(found
, state
, ev
->xkey
.time
);
1295 menu_frame_select(frame
, found
, TRUE
);
1297 menu_frame_select_next(frame
->child
);
1308 static gboolean
event_handle_menu(XEvent
*ev
)
1311 ObMenuEntryFrame
*e
;
1312 gboolean ret
= TRUE
;
1316 if ((ev
->xbutton
.button
< 4 || ev
->xbutton
.button
> 5)
1319 if ((e
= menu_entry_frame_under(ev
->xbutton
.x_root
,
1320 ev
->xbutton
.y_root
)))
1321 menu_entry_frame_execute(e
, ev
->xbutton
.state
,
1324 menu_frame_hide_all();
1328 if ((e
= g_hash_table_lookup(menu_frame_map
, &ev
->xcrossing
.window
))) {
1329 if (e
->ignore_enters
)
1332 menu_frame_select(e
->frame
, e
, FALSE
);
1336 if ((e
= g_hash_table_lookup(menu_frame_map
, &ev
->xcrossing
.window
)) &&
1337 (f
= find_active_menu()) && f
->selected
== e
&&
1338 e
->entry
->type
!= OB_MENU_ENTRY_TYPE_SUBMENU
)
1340 menu_frame_select(e
->frame
, NULL
, FALSE
);
1343 if ((e
= menu_entry_frame_under(ev
->xmotion
.x_root
,
1344 ev
->xmotion
.y_root
)))
1345 menu_frame_select(e
->frame
, e
, FALSE
);
1348 ret
= event_handle_menu_keyboard(ev
);
1354 static gboolean
menu_hide_delay_func(gpointer data
)
1356 menu_can_hide
= TRUE
;
1357 return FALSE
; /* no repeat */
1360 static void focus_delay_dest(gpointer data
)
1365 static gboolean
focus_delay_cmp(gconstpointer d1
, gconstpointer d2
)
1367 const ObFocusDelayData
*f1
= d1
;
1368 return f1
->client
== d2
;
1371 static gboolean
focus_delay_func(gpointer data
)
1373 ObFocusDelayData
*d
= data
;
1374 Time old
= event_curtime
;
1376 event_curtime
= d
->time
;
1377 if (focus_client
!= d
->client
) {
1378 if (client_focus(d
->client
) && config_focus_raise
)
1379 client_raise(d
->client
);
1381 event_curtime
= old
;
1382 return FALSE
; /* no repeat */
1385 static void focus_delay_client_dest(ObClient
*client
, gpointer data
)
1387 ob_main_loop_timeout_remove_data(ob_main_loop
, focus_delay_func
,
1391 void event_halt_focus_delay()
1393 ob_main_loop_timeout_remove(ob_main_loop
, focus_delay_func
);
1396 void event_ignore_queued_enters()
1398 GSList
*saved
= NULL
, *it
;
1401 XSync(ob_display
, FALSE
);
1403 /* count the events */
1405 e
= g_new(XEvent
, 1);
1406 if (XCheckTypedEvent(ob_display
, EnterNotify
, e
)) {
1409 win
= g_hash_table_lookup(window_map
, &e
->xany
.window
);
1410 if (win
&& WINDOW_IS_CLIENT(win
))
1411 ++ignore_enter_focus
;
1413 saved
= g_slist_append(saved
, e
);
1419 /* put the events back */
1420 for (it
= saved
; it
; it
= g_slist_next(it
)) {
1421 XPutBackEvent(ob_display
, it
->data
);
1424 g_slist_free(saved
);
1427 gboolean
event_time_after(Time t1
, Time t2
)
1429 g_assert(t1
!= CurrentTime
);
1430 g_assert(t2
!= CurrentTime
);
1433 Timestamp values wrap around (after about 49.7 days). The server, given
1434 its current time is represented by timestamp T, always interprets
1435 timestamps from clients by treating half of the timestamp space as being
1436 later in time than T.
1437 - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1440 /* TIME_HALF is half of the number space of a Time type variable */
1441 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1443 if (t2
>= TIME_HALF
)
1444 /* t2 is in the second half so t1 might wrap around and be smaller than
1446 return t1
>= t2
|| t1
< (t2
+ TIME_HALF
);
1448 /* t2 is in the first half so t1 has to come after it */
1449 return t1
>= t2
&& t1
< (t2
+ TIME_HALF
);