9 #include "framerender.h"
12 #include "extensions.h"
17 #include <X11/keysym.h>
18 #include <X11/Xatom.h>
19 #ifdef HAVE_SYS_SELECT_H
20 # include <sys/select.h>
23 static void event_process(XEvent
*e
);
24 static void event_handle_root(XEvent
*e
);
25 static void event_handle_client(Client
*c
, XEvent
*e
);
26 static void event_handle_menu(Menu
*menu
, XEvent
*e
);
28 Time event_lasttime
= 0;
30 /*! The value of the mask for the NumLock modifier */
31 unsigned int NumLockMask
;
32 /*! The value of the mask for the ScrollLock modifier */
33 unsigned int ScrollLockMask
;
34 /*! The key codes for the modifier keys */
35 static XModifierKeymap
*modmap
;
36 /*! Table of the constant modifier masks */
37 static const int mask_table
[] = {
38 ShiftMask
, LockMask
, ControlMask
, Mod1Mask
,
39 Mod2Mask
, Mod3Mask
, Mod4Mask
, Mod5Mask
41 static int mask_table_size
;
45 mask_table_size
= sizeof(mask_table
) / sizeof(mask_table
[0]);
47 /* get lock masks that are defined by the display (not constant) */
48 modmap
= XGetModifierMapping(ob_display
);
50 if (modmap
&& modmap
->max_keypermod
> 0) {
52 const size_t size
= mask_table_size
* modmap
->max_keypermod
;
53 /* get the values of the keyboard lock modifiers
54 Note: Caps lock is not retrieved the same way as Scroll and Num
55 lock since it doesn't need to be. */
56 const KeyCode num_lock
= XKeysymToKeycode(ob_display
, XK_Num_Lock
);
57 const KeyCode scroll_lock
= XKeysymToKeycode(ob_display
,
60 for (cnt
= 0; cnt
< size
; ++cnt
) {
61 if (! modmap
->modifiermap
[cnt
]) continue;
63 if (num_lock
== modmap
->modifiermap
[cnt
])
64 NumLockMask
= mask_table
[cnt
/ modmap
->max_keypermod
];
65 if (scroll_lock
== modmap
->modifiermap
[cnt
])
66 ScrollLockMask
= mask_table
[cnt
/ modmap
->max_keypermod
];
73 XFreeModifiermap(modmap
);
85 There are slightly different event retrieval semantics here for
86 local (or high bandwidth) versus remote (or low bandwidth)
87 connections to the display/Xserver.
90 if (!XPending(ob_display
))
94 This XSync allows for far more compression of events, which
95 makes things like Motion events perform far far better. Since
96 it also means network traffic for every event instead of every
97 X events (where X is the number retrieved at a time), it
98 probably should not be used for setups where Openbox is
99 running on a remote/low bandwidth display/Xserver.
101 XSync(ob_display
, FALSE
);
102 if (!XEventsQueued(ob_display
, QueuedAlready
))
105 XNextEvent(ob_display
, &e
);
110 timer_dispatch((GTimeVal
**)&wait
);
111 x_fd
= ConnectionNumber(ob_display
);
113 FD_SET(x_fd
, &selset
);
114 select(x_fd
+ 1, &selset
, NULL
, NULL
, wait
);
117 static Window
event_get_window(XEvent
*e
)
124 window
= e
->xmap
.window
;
127 window
= e
->xunmap
.window
;
130 window
= e
->xdestroywindow
.window
;
132 case ConfigureRequest
:
133 window
= e
->xconfigurerequest
.window
;
137 if (extensions_xkb
&& e
->type
== extensions_xkb_event_basep
) {
138 switch (((XkbAnyEvent
*)&e
)->xkb_type
) {
140 window
= ((XkbBellNotifyEvent
*)&e
)->window
;
146 window
= e
->xany
.window
;
151 static void event_set_lasttime(XEvent
*e
)
153 /* grab the lasttime and hack up the state */
157 event_lasttime
= e
->xbutton
.time
;
160 event_lasttime
= e
->xkey
.time
;
163 event_lasttime
= e
->xkey
.time
;
166 event_lasttime
= e
->xmotion
.time
;
169 event_lasttime
= e
->xproperty
.time
;
173 event_lasttime
= e
->xcrossing
.time
;
176 event_lasttime
= CurrentTime
;
181 #define STRIP_MODS(s) \
182 s &= ~(LockMask | NumLockMask | ScrollLockMask), \
183 /* kill off the Button1Mask etc, only want the modifiers */ \
184 s &= (ControlMask | ShiftMask | Mod1Mask | \
185 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
187 static void event_hack_mods(XEvent *e)
195 STRIP_MODS(e
->xbutton
.state
);
198 STRIP_MODS(e
->xkey
.state
);
201 STRIP_MODS(e
->xkey
.state
);
202 /* remove from the state the mask of the modifier being released, if
203 it is a modifier key being released (this is a little ugly..) */
204 kp
= modmap
->modifiermap
;
205 for (i
= 0; i
< mask_table_size
; ++i
) {
206 for (k
= 0; k
< modmap
->max_keypermod
; ++k
) {
207 if (*kp
== e
->xkey
.keycode
) { /* found the keycode */
208 /* remove the mask for it */
209 e
->xkey
.state
&= ~mask_table
[i
];
210 /* cause the first loop to break; */
212 break; /* get outta here! */
219 STRIP_MODS(e
->xmotion
.state
);
220 /* compress events */
223 while (XCheckTypedWindowEvent(ob_display
, e
->xmotion
.window
,
225 e
->xmotion
.x_root
= ce
.xmotion
.x_root
;
226 e
->xmotion
.y_root
= ce
.xmotion
.y_root
;
233 static gboolean
event_ignore(XEvent
*e
, Client
*client
)
238 g_message("FocusIn on %lx mode %d detail %d", window
,
239 e
->xfocus
.mode
, e
->xfocus
.detail
);
241 /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
242 because of RevertToPointerRoot. If the focus ends up reverting to
243 pointer root on a workspace change, then the FocusIn event that we
244 want will be of type NotifyAncestor. This situation does not occur
245 for FocusOut, so it is safely ignored there.
247 if (e
->xfocus
.detail
== NotifyInferior
||
248 e
->xfocus
.detail
> NotifyNonlinearVirtual
||
250 /* says a client was not found for the event (or a valid FocusIn
253 e
->xfocus
.window
= None
;
258 g_message("FocusIn on %lx", window
);
263 g_message("FocusOut on %lx mode %d detail %d", window
,
264 e
->xfocus
.mode
, e
->xfocus
.detail
);
266 if (e
->xfocus
.mode
== NotifyGrab
||
267 e
->xfocus
.detail
== NotifyInferior
||
268 e
->xfocus
.detail
== NotifyAncestor
||
269 e
->xfocus
.detail
> NotifyNonlinearVirtual
) return TRUE
;
272 g_message("FocusOut on %lx", window
);
274 /* Try process a FocusIn first, and if a legit one isn't found, then
275 do the fallback shiznit. */
278 gboolean isfo
= FALSE
;
280 if (XCheckTypedEvent(ob_display
, FocusIn
, &fi
)) {
283 /* when we have gotten a fi/fo pair, then see if there are any
284 more fo's coming. if there are, then don't fallback just yet
286 if ((isfo
= XCheckTypedEvent(ob_display
, FocusOut
, &fo
)))
287 XPutBackEvent(ob_display
, &fo
);
289 /* secret magic way of event_process telling us that no client
290 was found for the FocusIn event. ^_^ */
291 if (!isfo
&& fi
.xfocus
.window
== None
)
292 focus_fallback(Fallback_NoFocus
);
293 if (fi
.xfocus
.window
== e
->xfocus
.window
)
296 focus_fallback(Fallback_NoFocus
);
301 /* NotifyUngrab occurs when a mouse button is released and the event is
302 caused, like when lowering a window */
303 if (e
->xcrossing
.mode
== NotifyGrab
||
304 e
->xcrossing
.detail
== NotifyInferior
)
311 static void event_process(XEvent
*e
)
317 window
= event_get_window(e
);
318 if (!(client
= g_hash_table_lookup(client_map
, &window
)))
319 menu
= g_hash_table_lookup(menu_map
, &window
);
320 event_set_lasttime(e
);
322 if (event_ignore(e
, client
))
325 /* deal with it in the kernel */
327 event_handle_menu(menu
, e
);
329 event_handle_client(client
, e
);
330 else if (window
== ob_root
)
331 event_handle_root(e
);
332 else if (e
->type
== MapRequest
)
333 client_manage(window
);
334 else if (e
->type
== ConfigureRequest
) {
335 /* unhandled configure requests must be used to configure the
339 xwc
.x
= e
->xconfigurerequest
.x
;
340 xwc
.y
= e
->xconfigurerequest
.y
;
341 xwc
.width
= e
->xconfigurerequest
.width
;
342 xwc
.height
= e
->xconfigurerequest
.height
;
343 xwc
.border_width
= e
->xconfigurerequest
.border_width
;
344 xwc
.sibling
= e
->xconfigurerequest
.above
;
345 xwc
.stack_mode
= e
->xconfigurerequest
.detail
;
347 /* we are not to be held responsible if someone sends us an
349 xerror_set_ignore(TRUE
);
350 XConfigureWindow(ob_display
, window
,
351 e
->xconfigurerequest
.value_mask
, &xwc
);
352 xerror_set_ignore(FALSE
);
355 /* user input (action-bound) events */
357 if (e->type == ButtonPress || e->type == ButtonRelease ||
358 e->type == MotionNotify)
359 mouse_event(e, client);
360 else if (e->type == KeyPress || e->type == KeyRelease)
364 /* dispatch the event to registered handlers */
365 dispatch_x(e
, client
);
368 static void event_handle_root(XEvent
*e
)
374 if (e
->xclient
.format
!= 32) break;
376 msgtype
= e
->xclient
.message_type
;
377 if (msgtype
== prop_atoms
.net_current_desktop
) {
378 unsigned int d
= e
->xclient
.data
.l
[0];
379 if (d
< screen_num_desktops
)
380 screen_set_desktop(d
);
381 } else if (msgtype
== prop_atoms
.net_number_of_desktops
) {
382 unsigned int d
= e
->xclient
.data
.l
[0];
384 screen_set_num_desktops(d
);
385 } else if (msgtype
== prop_atoms
.net_showing_desktop
) {
386 screen_show_desktop(e
->xclient
.data
.l
[0] != 0);
390 if (e
->xproperty
.atom
== prop_atoms
.net_desktop_names
)
391 screen_update_desktop_names();
392 else if (e
->xproperty
.atom
== prop_atoms
.net_desktop_layout
)
393 screen_update_layout();
398 static void event_handle_client(Client
*client
, XEvent
*e
)
407 switch (frame_context(client
, e
->xbutton
.window
)) {
408 case Context_Maximize
:
409 client
->frame
->max_press
= (e
->type
== ButtonPress
);
410 framerender_frame(client
->frame
);
413 client
->frame
->close_press
= (e
->type
== ButtonPress
);
414 framerender_frame(client
->frame
);
416 case Context_Iconify
:
417 client
->frame
->iconify_press
= (e
->type
== ButtonPress
);
418 framerender_frame(client
->frame
);
420 case Context_AllDesktops
:
421 client
->frame
->desk_press
= (e
->type
== ButtonPress
);
422 framerender_frame(client
->frame
);
425 client
->frame
->shade_press
= (e
->type
== ButtonPress
);
426 framerender_frame(client
->frame
);
429 /* nothing changes with clicks for any other contexts */
434 focus_set_client(client
);
437 g_message("Focus%s on client for %lx", (e
->type
==FocusIn
?"In":"Out"),
440 /* focus state can affect the stacking layer */
441 client_calc_layer(client
);
442 frame_adjust_focus(client
->frame
);
445 if (client_normal(client
)) {
446 if (ob_state
== State_Starting
) {
447 /* move it to the top of the focus order */
448 guint desktop
= client
->desktop
;
449 if (desktop
== DESKTOP_ALL
) desktop
= screen_desktop
;
450 focus_order
[desktop
] = g_list_remove(focus_order
[desktop
],
452 focus_order
[desktop
] = g_list_prepend(focus_order
[desktop
],
454 } else if (config_focus_follow
) {
456 g_message("EnterNotify on %lx, focusing window",
459 client_focus(client
);
463 case ConfigureRequest
:
465 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
466 ConfigureRequest
, &ce
)) {
468 /* XXX if this causes bad things.. we can compress config req's
469 with the same mask. */
470 e
->xconfigurerequest
.value_mask
|=
471 ce
.xconfigurerequest
.value_mask
;
472 if (ce
.xconfigurerequest
.value_mask
& CWX
)
473 e
->xconfigurerequest
.x
= ce
.xconfigurerequest
.x
;
474 if (ce
.xconfigurerequest
.value_mask
& CWY
)
475 e
->xconfigurerequest
.y
= ce
.xconfigurerequest
.y
;
476 if (ce
.xconfigurerequest
.value_mask
& CWWidth
)
477 e
->xconfigurerequest
.width
= ce
.xconfigurerequest
.width
;
478 if (ce
.xconfigurerequest
.value_mask
& CWHeight
)
479 e
->xconfigurerequest
.height
= ce
.xconfigurerequest
.height
;
480 if (ce
.xconfigurerequest
.value_mask
& CWBorderWidth
)
481 e
->xconfigurerequest
.border_width
=
482 ce
.xconfigurerequest
.border_width
;
483 if (ce
.xconfigurerequest
.value_mask
& CWStackMode
)
484 e
->xconfigurerequest
.detail
= ce
.xconfigurerequest
.detail
;
487 /* if we are iconic (or shaded (fvwm does this)) ignore the event */
488 if (client
->iconic
|| client
->shaded
) return;
490 if (e
->xconfigurerequest
.value_mask
& CWBorderWidth
)
491 client
->border_width
= e
->xconfigurerequest
.border_width
;
493 /* resize, then move, as specified in the EWMH section 7.7 */
494 if (e
->xconfigurerequest
.value_mask
& (CWWidth
| CWHeight
|
499 x
= (e
->xconfigurerequest
.value_mask
& CWX
) ?
500 e
->xconfigurerequest
.x
: client
->area
.x
;
501 y
= (e
->xconfigurerequest
.value_mask
& CWY
) ?
502 e
->xconfigurerequest
.y
: client
->area
.y
;
503 w
= (e
->xconfigurerequest
.value_mask
& CWWidth
) ?
504 e
->xconfigurerequest
.width
: client
->area
.width
;
505 h
= (e
->xconfigurerequest
.value_mask
& CWHeight
) ?
506 e
->xconfigurerequest
.height
: client
->area
.height
;
508 switch (client
->gravity
) {
509 case NorthEastGravity
:
511 corner
= Corner_TopRight
;
513 case SouthWestGravity
:
515 corner
= Corner_BottomLeft
;
517 case SouthEastGravity
:
518 corner
= Corner_BottomRight
;
520 default: /* NorthWest, Static, etc */
521 corner
= Corner_TopLeft
;
524 client_configure(client
, corner
, x
, y
, w
, h
, FALSE
, FALSE
);
527 if (e
->xconfigurerequest
.value_mask
& CWStackMode
) {
528 switch (e
->xconfigurerequest
.detail
) {
531 stacking_lower(client
);
537 stacking_raise(client
);
543 if (client
->ignore_unmaps
) {
544 client
->ignore_unmaps
--;
547 client_unmanage(client
);
550 client_unmanage(client
);
553 /* this is when the client is first taken captive in the frame */
554 if (e
->xreparent
.parent
== client
->frame
->plate
) break;
557 This event is quite rare and is usually handled in unmapHandler.
558 However, if the window is unmapped when the reparent event occurs,
559 the window manager never sees it because an unmap event is not sent
560 to an already unmapped window.
563 /* we don't want the reparent event, put it back on the stack for the
564 X server to deal with after we unmanage the window */
565 XPutBackEvent(ob_display
, e
);
567 client_unmanage(client
);
570 g_message("MapRequest for 0x%lx", client
->window
);
571 if (!client
->iconic
) break; /* this normally doesn't happen, but if it
572 does, we don't want it! */
573 if (screen_showing_desktop
)
574 screen_show_desktop(FALSE
);
575 client_iconify(client
, FALSE
, TRUE
);
576 if (!client
->frame
->visible
)
577 /* if its not visible still, then don't mess with it */
580 client_shade(client
, FALSE
);
581 client_focus(client
);
582 stacking_raise(client
);
585 /* validate cuz we query stuff off the client here */
586 if (!client_validate(client
)) break;
588 if (e
->xclient
.format
!= 32) return;
590 msgtype
= e
->xclient
.message_type
;
591 if (msgtype
== prop_atoms
.wm_change_state
) {
592 /* compress changes into a single change */
593 while (XCheckTypedWindowEvent(ob_display
, e
->type
,
594 client
->window
, &ce
)) {
595 /* XXX: it would be nice to compress ALL messages of a
596 type, not just messages in a row without other
597 message types between. */
598 if (ce
.xclient
.message_type
!= msgtype
) {
599 XPutBackEvent(ob_display
, &ce
);
602 e
->xclient
= ce
.xclient
;
604 client_set_wm_state(client
, e
->xclient
.data
.l
[0]);
605 } else if (msgtype
== prop_atoms
.net_wm_desktop
) {
606 /* compress changes into a single change */
607 while (XCheckTypedWindowEvent(ob_display
, e
->type
,
608 client
->window
, &ce
)) {
609 /* XXX: it would be nice to compress ALL messages of a
610 type, not just messages in a row without other
611 message types between. */
612 if (ce
.xclient
.message_type
!= msgtype
) {
613 XPutBackEvent(ob_display
, &ce
);
616 e
->xclient
= ce
.xclient
;
618 if ((unsigned)e
->xclient
.data
.l
[0] < screen_num_desktops
||
619 (unsigned)e
->xclient
.data
.l
[0] == DESKTOP_ALL
)
620 client_set_desktop(client
, (unsigned)e
->xclient
.data
.l
[0],
622 } else if (msgtype
== prop_atoms
.net_wm_state
) {
623 /* can't compress these */
624 g_message("net_wm_state %s %ld %ld for 0x%lx",
625 (e
->xclient
.data
.l
[0] == 0 ? "Remove" :
626 e
->xclient
.data
.l
[0] == 1 ? "Add" :
627 e
->xclient
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
628 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2],
630 client_set_state(client
, e
->xclient
.data
.l
[0],
631 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2]);
632 } else if (msgtype
== prop_atoms
.net_close_window
) {
633 g_message("net_close_window for 0x%lx", client
->window
);
634 client_close(client
);
635 } else if (msgtype
== prop_atoms
.net_active_window
) {
636 g_message("net_active_window for 0x%lx", client
->window
);
637 if (screen_showing_desktop
)
638 screen_show_desktop(FALSE
);
640 client_iconify(client
, FALSE
, TRUE
);
641 else if (!client
->frame
->visible
)
642 /* if its not visible for other reasons, then don't mess
646 client_shade(client
, FALSE
);
647 client_focus(client
);
648 stacking_raise(client
);
652 /* validate cuz we query stuff off the client here */
653 if (!client_validate(client
)) break;
655 /* compress changes to a single property into a single change */
656 while (XCheckTypedWindowEvent(ob_display
, e
->type
,
657 client
->window
, &ce
)) {
658 /* XXX: it would be nice to compress ALL changes to a property,
659 not just changes in a row without other props between. */
660 if (ce
.xproperty
.atom
!= e
->xproperty
.atom
) {
661 XPutBackEvent(ob_display
, &ce
);
666 msgtype
= e
->xproperty
.atom
;
667 if (msgtype
== XA_WM_NORMAL_HINTS
) {
668 client_update_normal_hints(client
);
669 /* normal hints can make a window non-resizable */
670 client_setup_decor_and_functions(client
);
672 else if (msgtype
== XA_WM_HINTS
)
673 client_update_wmhints(client
);
674 else if (msgtype
== XA_WM_TRANSIENT_FOR
) {
675 client_update_transient_for(client
);
676 client_get_type(client
);
677 /* type may have changed, so update the layer */
678 client_calc_layer(client
);
679 client_setup_decor_and_functions(client
);
681 else if (msgtype
== prop_atoms
.net_wm_name
||
682 msgtype
== prop_atoms
.wm_name
)
683 client_update_title(client
);
684 else if (msgtype
== prop_atoms
.net_wm_icon_name
||
685 msgtype
== prop_atoms
.wm_icon_name
)
686 client_update_icon_title(client
);
687 else if (msgtype
== prop_atoms
.wm_class
)
688 client_update_class(client
);
689 else if (msgtype
== prop_atoms
.wm_protocols
) {
690 client_update_protocols(client
);
691 client_setup_decor_and_functions(client
);
693 else if (msgtype
== prop_atoms
.net_wm_strut
)
694 client_update_strut(client
);
695 else if (msgtype
== prop_atoms
.net_wm_icon
)
696 client_update_icons(client
);
697 else if (msgtype
== prop_atoms
.kwm_win_icon
)
698 client_update_kwm_icon(client
);
702 if (extensions_shape
&& e
->type
== extensions_shape_event_basep
) {
703 client
->shaped
= ((XShapeEvent
*)e
)->shaped
;
704 frame_adjust_shape(client
->frame
);
710 static void event_handle_menu(Menu
*menu
, XEvent
*e
)
717 g_message("enter/leave");
718 entry
= menu_find_entry(menu
, e
->xcrossing
.window
);
720 entry
->hilite
= e
->type
== EnterNotify
;
721 menu_entry_render(entry
);