8 #include "framerender.h"
11 #include "extensions.h"
16 #include <X11/keysym.h>
17 #include <X11/Xatom.h>
18 #ifdef HAVE_SYS_SELECT_H
19 # include <sys/select.h>
22 static void event_process(XEvent
*e
);
23 static void event_handle_root(XEvent
*e
);
24 static void event_handle_client(Client
*c
, XEvent
*e
);
26 Time event_lasttime
= 0;
28 /*! The value of the mask for the NumLock modifier */
29 unsigned int NumLockMask
;
30 /*! The value of the mask for the ScrollLock modifier */
31 unsigned int ScrollLockMask
;
32 /*! The key codes for the modifier keys */
33 static XModifierKeymap
*modmap
;
34 /*! Table of the constant modifier masks */
35 static const int mask_table
[] = {
36 ShiftMask
, LockMask
, ControlMask
, Mod1Mask
,
37 Mod2Mask
, Mod3Mask
, Mod4Mask
, Mod5Mask
39 static int mask_table_size
;
43 mask_table_size
= sizeof(mask_table
) / sizeof(mask_table
[0]);
45 /* get lock masks that are defined by the display (not constant) */
46 modmap
= XGetModifierMapping(ob_display
);
48 if (modmap
&& modmap
->max_keypermod
> 0) {
50 const size_t size
= mask_table_size
* modmap
->max_keypermod
;
51 /* get the values of the keyboard lock modifiers
52 Note: Caps lock is not retrieved the same way as Scroll and Num
53 lock since it doesn't need to be. */
54 const KeyCode num_lock
= XKeysymToKeycode(ob_display
, XK_Num_Lock
);
55 const KeyCode scroll_lock
= XKeysymToKeycode(ob_display
,
58 for (cnt
= 0; cnt
< size
; ++cnt
) {
59 if (! modmap
->modifiermap
[cnt
]) continue;
61 if (num_lock
== modmap
->modifiermap
[cnt
])
62 NumLockMask
= mask_table
[cnt
/ modmap
->max_keypermod
];
63 if (scroll_lock
== modmap
->modifiermap
[cnt
])
64 ScrollLockMask
= mask_table
[cnt
/ modmap
->max_keypermod
];
71 XFreeModifiermap(modmap
);
83 There are slightly different event retrieval semantics here for
84 local (or high bandwidth) versus remote (or low bandwidth)
85 connections to the display/Xserver.
88 if (!XPending(ob_display
))
92 This XSync allows for far more compression of events, which
93 makes things like Motion events perform far far better. Since
94 it also means network traffic for every event instead of every
95 X events (where X is the number retrieved at a time), it
96 probably should not be used for setups where Openbox is
97 running on a remote/low bandwidth display/Xserver.
99 XSync(ob_display
, FALSE
);
100 if (!XEventsQueued(ob_display
, QueuedAlready
))
103 XNextEvent(ob_display
, &e
);
108 timer_dispatch((GTimeVal
**)&wait
);
109 x_fd
= ConnectionNumber(ob_display
);
111 FD_SET(x_fd
, &selset
);
112 select(x_fd
+ 1, &selset
, NULL
, NULL
, wait
);
115 static Window
event_get_window(XEvent
*e
)
122 window
= e
->xmap
.window
;
125 window
= e
->xunmap
.window
;
128 window
= e
->xdestroywindow
.window
;
130 case ConfigureRequest
:
131 window
= e
->xconfigurerequest
.window
;
135 if (extensions_xkb
&& e
->type
== extensions_xkb_event_basep
) {
136 switch (((XkbAnyEvent
*)&e
)->xkb_type
) {
138 window
= ((XkbBellNotifyEvent
*)&e
)->window
;
144 window
= e
->xany
.window
;
149 static void event_set_lasttime(XEvent
*e
)
151 /* grab the lasttime and hack up the state */
155 event_lasttime
= e
->xbutton
.time
;
158 event_lasttime
= e
->xkey
.time
;
161 event_lasttime
= e
->xkey
.time
;
164 event_lasttime
= e
->xmotion
.time
;
167 event_lasttime
= e
->xproperty
.time
;
171 event_lasttime
= e
->xcrossing
.time
;
174 event_lasttime
= CurrentTime
;
179 #define STRIP_MODS(s) \
180 s &= ~(LockMask | NumLockMask | ScrollLockMask), \
181 /* kill off the Button1Mask etc, only want the modifiers */ \
182 s &= (ControlMask | ShiftMask | Mod1Mask | \
183 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
185 static void event_hack_mods(XEvent *e)
193 STRIP_MODS(e
->xbutton
.state
);
196 STRIP_MODS(e
->xkey
.state
);
199 STRIP_MODS(e
->xkey
.state
);
200 /* remove from the state the mask of the modifier being released, if
201 it is a modifier key being released (this is a little ugly..) */
202 kp
= modmap
->modifiermap
;
203 for (i
= 0; i
< mask_table_size
; ++i
) {
204 for (k
= 0; k
< modmap
->max_keypermod
; ++k
) {
205 if (*kp
== e
->xkey
.keycode
) { /* found the keycode */
206 /* remove the mask for it */
207 e
->xkey
.state
&= ~mask_table
[i
];
208 /* cause the first loop to break; */
210 break; /* get outta here! */
217 STRIP_MODS(e
->xmotion
.state
);
218 /* compress events */
221 while (XCheckTypedWindowEvent(ob_display
, e
->xmotion
.window
,
223 e
->xmotion
.x_root
= ce
.xmotion
.x_root
;
224 e
->xmotion
.y_root
= ce
.xmotion
.y_root
;
231 static gboolean
event_ignore(XEvent
*e
, Client
*client
)
236 g_message("FocusIn on %lx mode %d detail %d", window
,
237 e
->xfocus
.mode
, e
->xfocus
.detail
);
239 /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
240 because of RevertToPointerRoot. If the focus ends up reverting to
241 pointer root on a workspace change, then the FocusIn event that we
242 want will be of type NotifyAncestor. This situation does not occur
243 for FocusOut, so it is safely ignored there.
245 if (e
->xfocus
.detail
== NotifyInferior
||
246 e
->xfocus
.detail
> NotifyNonlinearVirtual
||
248 /* says a client was not found for the event (or a valid FocusIn
251 e
->xfocus
.window
= None
;
256 g_message("FocusIn on %lx", window
);
261 g_message("FocusOut on %lx mode %d detail %d", window
,
262 e
->xfocus
.mode
, e
->xfocus
.detail
);
264 if (e
->xfocus
.mode
== NotifyGrab
||
265 e
->xfocus
.detail
== NotifyInferior
||
266 e
->xfocus
.detail
== NotifyAncestor
||
267 e
->xfocus
.detail
> NotifyNonlinearVirtual
) return TRUE
;
270 g_message("FocusOut on %lx", window
);
272 /* Try process a FocusIn first, and if a legit one isn't found, then
273 do the fallback shiznit. */
276 gboolean isfo
= FALSE
;
278 if (XCheckTypedEvent(ob_display
, FocusIn
, &fi
)) {
281 /* when we have gotten a fi/fo pair, then see if there are any
282 more fo's coming. if there are, then don't fallback just yet
284 if ((isfo
= XCheckTypedEvent(ob_display
, FocusOut
, &fo
)))
285 XPutBackEvent(ob_display
, &fo
);
287 /* secret magic way of event_process telling us that no client
288 was found for the FocusIn event. ^_^ */
289 if (!isfo
&& fi
.xfocus
.window
== None
)
290 focus_fallback(Fallback_NoFocus
);
291 if (fi
.xfocus
.window
== e
->xfocus
.window
)
294 focus_fallback(Fallback_NoFocus
);
299 /* NotifyUngrab occurs when a mouse button is released and the event is
300 caused, like when lowering a window */
301 if (e
->xcrossing
.mode
== NotifyGrab
||
302 e
->xcrossing
.detail
== NotifyInferior
)
309 static void event_process(XEvent
*e
)
314 window
= event_get_window(e
);
315 client
= g_hash_table_lookup(client_map
, &window
);
316 event_set_lasttime(e
);
318 if (event_ignore(e
, client
))
321 /* deal with it in the kernel */
323 event_handle_client(client
, e
);
324 else if (window
== ob_root
)
325 event_handle_root(e
);
326 else if (e
->type
== MapRequest
)
327 client_manage(window
);
328 else if (e
->type
== ConfigureRequest
) {
329 /* unhandled configure requests must be used to configure the
333 xwc
.x
= e
->xconfigurerequest
.x
;
334 xwc
.y
= e
->xconfigurerequest
.y
;
335 xwc
.width
= e
->xconfigurerequest
.width
;
336 xwc
.height
= e
->xconfigurerequest
.height
;
337 xwc
.border_width
= e
->xconfigurerequest
.border_width
;
338 xwc
.sibling
= e
->xconfigurerequest
.above
;
339 xwc
.stack_mode
= e
->xconfigurerequest
.detail
;
341 /* we are not to be held responsible if someone sends us an
343 xerror_set_ignore(TRUE
);
344 XConfigureWindow(ob_display
, window
,
345 e
->xconfigurerequest
.value_mask
, &xwc
);
346 xerror_set_ignore(FALSE
);
349 /* user input (action-bound) events */
351 if (e->type == ButtonPress || e->type == ButtonRelease ||
352 e->type == MotionNotify)
353 mouse_event(e, client);
354 else if (e->type == KeyPress || e->type == KeyRelease)
358 /* dispatch the event to registered handlers */
359 dispatch_x(e
, client
);
362 static void event_handle_root(XEvent
*e
)
368 if (e
->xclient
.format
!= 32) break;
370 msgtype
= e
->xclient
.message_type
;
371 if (msgtype
== prop_atoms
.net_current_desktop
) {
372 unsigned int d
= e
->xclient
.data
.l
[0];
373 if (d
< screen_num_desktops
)
374 screen_set_desktop(d
);
375 } else if (msgtype
== prop_atoms
.net_number_of_desktops
) {
376 unsigned int d
= e
->xclient
.data
.l
[0];
378 screen_set_num_desktops(d
);
379 } else if (msgtype
== prop_atoms
.net_showing_desktop
) {
380 screen_show_desktop(e
->xclient
.data
.l
[0] != 0);
384 if (e
->xproperty
.atom
== prop_atoms
.net_desktop_names
)
385 screen_update_desktop_names();
386 else if (e
->xproperty
.atom
== prop_atoms
.net_desktop_layout
)
387 screen_update_layout();
392 static void event_handle_client(Client
*client
, XEvent
*e
)
401 switch (frame_context(client
, e
->xbutton
.window
)) {
402 case Context_Maximize
:
403 client
->frame
->max_press
= (e
->type
== ButtonPress
);
404 framerender_frame(client
->frame
);
407 client
->frame
->close_press
= (e
->type
== ButtonPress
);
408 framerender_frame(client
->frame
);
410 case Context_Iconify
:
411 client
->frame
->iconify_press
= (e
->type
== ButtonPress
);
412 framerender_frame(client
->frame
);
414 case Context_AllDesktops
:
415 client
->frame
->desk_press
= (e
->type
== ButtonPress
);
416 framerender_frame(client
->frame
);
419 client
->frame
->shade_press
= (e
->type
== ButtonPress
);
420 framerender_frame(client
->frame
);
423 /* nothing changes with clicks for any other contexts */
428 focus_set_client(client
);
431 g_message("Focus%s on client for %lx", (e
->type
==FocusIn
?"In":"Out"),
434 /* focus state can affect the stacking layer */
435 client_calc_layer(client
);
436 frame_adjust_focus(client
->frame
);
439 if (client_normal(client
)) {
440 if (ob_state
== State_Starting
) {
441 /* move it to the top of the focus order */
442 guint desktop
= client
->desktop
;
443 if (desktop
== DESKTOP_ALL
) desktop
= screen_desktop
;
444 focus_order
[desktop
] = g_list_remove(focus_order
[desktop
],
446 focus_order
[desktop
] = g_list_prepend(focus_order
[desktop
],
448 } else if (config_focus_follow
) {
450 g_message("EnterNotify on %lx, focusing window",
453 client_focus(client
);
457 case ConfigureRequest
:
459 while (XCheckTypedWindowEvent(ob_display
, client
->window
,
460 ConfigureRequest
, &ce
)) {
462 /* XXX if this causes bad things.. we can compress config req's
463 with the same mask. */
464 e
->xconfigurerequest
.value_mask
|=
465 ce
.xconfigurerequest
.value_mask
;
466 if (ce
.xconfigurerequest
.value_mask
& CWX
)
467 e
->xconfigurerequest
.x
= ce
.xconfigurerequest
.x
;
468 if (ce
.xconfigurerequest
.value_mask
& CWY
)
469 e
->xconfigurerequest
.y
= ce
.xconfigurerequest
.y
;
470 if (ce
.xconfigurerequest
.value_mask
& CWWidth
)
471 e
->xconfigurerequest
.width
= ce
.xconfigurerequest
.width
;
472 if (ce
.xconfigurerequest
.value_mask
& CWHeight
)
473 e
->xconfigurerequest
.height
= ce
.xconfigurerequest
.height
;
474 if (ce
.xconfigurerequest
.value_mask
& CWBorderWidth
)
475 e
->xconfigurerequest
.border_width
=
476 ce
.xconfigurerequest
.border_width
;
477 if (ce
.xconfigurerequest
.value_mask
& CWStackMode
)
478 e
->xconfigurerequest
.detail
= ce
.xconfigurerequest
.detail
;
481 /* if we are iconic (or shaded (fvwm does this)) ignore the event */
482 if (client
->iconic
|| client
->shaded
) return;
484 if (e
->xconfigurerequest
.value_mask
& CWBorderWidth
)
485 client
->border_width
= e
->xconfigurerequest
.border_width
;
487 /* resize, then move, as specified in the EWMH section 7.7 */
488 if (e
->xconfigurerequest
.value_mask
& (CWWidth
| CWHeight
|
493 x
= (e
->xconfigurerequest
.value_mask
& CWX
) ?
494 e
->xconfigurerequest
.x
: client
->area
.x
;
495 y
= (e
->xconfigurerequest
.value_mask
& CWY
) ?
496 e
->xconfigurerequest
.y
: client
->area
.y
;
497 w
= (e
->xconfigurerequest
.value_mask
& CWWidth
) ?
498 e
->xconfigurerequest
.width
: client
->area
.width
;
499 h
= (e
->xconfigurerequest
.value_mask
& CWHeight
) ?
500 e
->xconfigurerequest
.height
: client
->area
.height
;
502 switch (client
->gravity
) {
503 case NorthEastGravity
:
505 corner
= Corner_TopRight
;
507 case SouthWestGravity
:
509 corner
= Corner_BottomLeft
;
511 case SouthEastGravity
:
512 corner
= Corner_BottomRight
;
514 default: /* NorthWest, Static, etc */
515 corner
= Corner_TopLeft
;
518 client_configure(client
, corner
, x
, y
, w
, h
, FALSE
, FALSE
);
521 if (e
->xconfigurerequest
.value_mask
& CWStackMode
) {
522 switch (e
->xconfigurerequest
.detail
) {
525 stacking_lower(client
);
531 stacking_raise(client
);
537 if (client
->ignore_unmaps
) {
538 client
->ignore_unmaps
--;
541 client_unmanage(client
);
544 client_unmanage(client
);
547 /* this is when the client is first taken captive in the frame */
548 if (e
->xreparent
.parent
== client
->frame
->plate
) break;
551 This event is quite rare and is usually handled in unmapHandler.
552 However, if the window is unmapped when the reparent event occurs,
553 the window manager never sees it because an unmap event is not sent
554 to an already unmapped window.
557 /* we don't want the reparent event, put it back on the stack for the
558 X server to deal with after we unmanage the window */
559 XPutBackEvent(ob_display
, e
);
561 client_unmanage(client
);
564 g_message("MapRequest for 0x%lx", client
->window
);
565 if (!client
->iconic
) break; /* this normally doesn't happen, but if it
566 does, we don't want it! */
567 if (screen_showing_desktop
)
568 screen_show_desktop(FALSE
);
569 client_iconify(client
, FALSE
, TRUE
);
570 if (!client
->frame
->visible
)
571 /* if its not visible still, then don't mess with it */
574 client_shade(client
, FALSE
);
575 client_focus(client
);
576 stacking_raise(client
);
579 /* validate cuz we query stuff off the client here */
580 if (!client_validate(client
)) break;
582 if (e
->xclient
.format
!= 32) return;
584 msgtype
= e
->xclient
.message_type
;
585 if (msgtype
== prop_atoms
.wm_change_state
) {
586 /* compress changes into a single change */
587 while (XCheckTypedWindowEvent(ob_display
, e
->type
,
588 client
->window
, &ce
)) {
589 /* XXX: it would be nice to compress ALL messages of a
590 type, not just messages in a row without other
591 message types between. */
592 if (ce
.xclient
.message_type
!= msgtype
) {
593 XPutBackEvent(ob_display
, &ce
);
596 e
->xclient
= ce
.xclient
;
598 client_set_wm_state(client
, e
->xclient
.data
.l
[0]);
599 } else if (msgtype
== prop_atoms
.net_wm_desktop
) {
600 /* compress changes into a single change */
601 while (XCheckTypedWindowEvent(ob_display
, e
->type
,
602 client
->window
, &ce
)) {
603 /* XXX: it would be nice to compress ALL messages of a
604 type, not just messages in a row without other
605 message types between. */
606 if (ce
.xclient
.message_type
!= msgtype
) {
607 XPutBackEvent(ob_display
, &ce
);
610 e
->xclient
= ce
.xclient
;
612 if ((unsigned)e
->xclient
.data
.l
[0] < screen_num_desktops
||
613 (unsigned)e
->xclient
.data
.l
[0] == DESKTOP_ALL
)
614 client_set_desktop(client
, (unsigned)e
->xclient
.data
.l
[0],
616 } else if (msgtype
== prop_atoms
.net_wm_state
) {
617 /* can't compress these */
618 g_message("net_wm_state %s %ld %ld for 0x%lx",
619 (e
->xclient
.data
.l
[0] == 0 ? "Remove" :
620 e
->xclient
.data
.l
[0] == 1 ? "Add" :
621 e
->xclient
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
622 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2],
624 client_set_state(client
, e
->xclient
.data
.l
[0],
625 e
->xclient
.data
.l
[1], e
->xclient
.data
.l
[2]);
626 } else if (msgtype
== prop_atoms
.net_close_window
) {
627 g_message("net_close_window for 0x%lx", client
->window
);
628 client_close(client
);
629 } else if (msgtype
== prop_atoms
.net_active_window
) {
630 g_message("net_active_window for 0x%lx", client
->window
);
631 if (screen_showing_desktop
)
632 screen_show_desktop(FALSE
);
634 client_iconify(client
, FALSE
, TRUE
);
635 else if (!client
->frame
->visible
)
636 /* if its not visible for other reasons, then don't mess
640 client_shade(client
, FALSE
);
641 client_focus(client
);
642 stacking_raise(client
);
646 /* validate cuz we query stuff off the client here */
647 if (!client_validate(client
)) break;
649 /* compress changes to a single property into a single change */
650 while (XCheckTypedWindowEvent(ob_display
, e
->type
,
651 client
->window
, &ce
)) {
652 /* XXX: it would be nice to compress ALL changes to a property,
653 not just changes in a row without other props between. */
654 if (ce
.xproperty
.atom
!= e
->xproperty
.atom
) {
655 XPutBackEvent(ob_display
, &ce
);
660 msgtype
= e
->xproperty
.atom
;
661 if (msgtype
== XA_WM_NORMAL_HINTS
) {
662 client_update_normal_hints(client
);
663 /* normal hints can make a window non-resizable */
664 client_setup_decor_and_functions(client
);
666 else if (msgtype
== XA_WM_HINTS
)
667 client_update_wmhints(client
);
668 else if (msgtype
== XA_WM_TRANSIENT_FOR
) {
669 client_update_transient_for(client
);
670 client_get_type(client
);
671 /* type may have changed, so update the layer */
672 client_calc_layer(client
);
673 client_setup_decor_and_functions(client
);
675 else if (msgtype
== prop_atoms
.net_wm_name
||
676 msgtype
== prop_atoms
.wm_name
)
677 client_update_title(client
);
678 else if (msgtype
== prop_atoms
.net_wm_icon_name
||
679 msgtype
== prop_atoms
.wm_icon_name
)
680 client_update_icon_title(client
);
681 else if (msgtype
== prop_atoms
.wm_class
)
682 client_update_class(client
);
683 else if (msgtype
== prop_atoms
.wm_protocols
) {
684 client_update_protocols(client
);
685 client_setup_decor_and_functions(client
);
687 else if (msgtype
== prop_atoms
.net_wm_strut
)
688 client_update_strut(client
);
689 else if (msgtype
== prop_atoms
.net_wm_icon
)
690 client_update_icons(client
);
691 else if (msgtype
== prop_atoms
.kwm_win_icon
)
692 client_update_kwm_icon(client
);
696 if (extensions_shape
&& e
->type
== extensions_shape_event_basep
) {
697 client
->shaped
= ((XShapeEvent
*)e
)->shaped
;
698 frame_adjust_shape(client
->frame
);