]> Dogcows Code - chaz/openbox/blob - openbox/event.c
clean up this mess of crap a lot
[chaz/openbox] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "xerror.h"
4 #include "prop.h"
5 #include "config.h"
6 #include "screen.h"
7 #include "frame.h"
8 #include "framerender.h"
9 #include "focus.h"
10 #include "stacking.h"
11 #include "extensions.h"
12 #include "timer.h"
13 #include "dispatch.h"
14
15 #include <X11/Xlib.h>
16 #include <X11/keysym.h>
17 #include <X11/Xatom.h>
18 #ifdef HAVE_SYS_SELECT_H
19 # include <sys/select.h>
20 #endif
21
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);
25
26 Time event_lasttime = 0;
27
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
38 };
39 static int mask_table_size;
40
41 void event_startup()
42 {
43 mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
44
45 /* get lock masks that are defined by the display (not constant) */
46 modmap = XGetModifierMapping(ob_display);
47 g_assert(modmap);
48 if (modmap && modmap->max_keypermod > 0) {
49 size_t cnt;
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,
56 XK_Scroll_Lock);
57
58 for (cnt = 0; cnt < size; ++cnt) {
59 if (! modmap->modifiermap[cnt]) continue;
60
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];
65 }
66 }
67 }
68
69 void event_shutdown()
70 {
71 XFreeModifiermap(modmap);
72 }
73
74 void event_loop()
75 {
76 fd_set selset;
77 XEvent e;
78 int x_fd;
79 struct timeval *wait;
80
81 while (TRUE) {
82 /*
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.
86 */
87 if (ob_remote) {
88 if (!XPending(ob_display))
89 break;
90 } else {
91 /*
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.
98 */
99 XSync(ob_display, FALSE);
100 if (!XEventsQueued(ob_display, QueuedAlready))
101 break;
102 }
103 XNextEvent(ob_display, &e);
104
105 event_process(&e);
106 }
107
108 timer_dispatch((GTimeVal**)&wait);
109 x_fd = ConnectionNumber(ob_display);
110 FD_ZERO(&selset);
111 FD_SET(x_fd, &selset);
112 select(x_fd + 1, &selset, NULL, NULL, wait);
113 }
114
115 static Window event_get_window(XEvent *e)
116 {
117 Window window;
118
119 /* pick a window */
120 switch (e->type) {
121 case MapRequest:
122 window = e->xmap.window;
123 break;
124 case UnmapNotify:
125 window = e->xunmap.window;
126 break;
127 case DestroyNotify:
128 window = e->xdestroywindow.window;
129 break;
130 case ConfigureRequest:
131 window = e->xconfigurerequest.window;
132 break;
133 default:
134 #ifdef XKB
135 if (extensions_xkb && e->type == extensions_xkb_event_basep) {
136 switch (((XkbAnyEvent*)&e)->xkb_type) {
137 case XkbBellNotify:
138 window = ((XkbBellNotifyEvent*)&e)->window;
139 default:
140 window = None;
141 }
142 } else
143 #endif
144 window = e->xany.window;
145 }
146 return window;
147 }
148
149 static void event_set_lasttime(XEvent *e)
150 {
151 /* grab the lasttime and hack up the state */
152 switch (e->type) {
153 case ButtonPress:
154 case ButtonRelease:
155 event_lasttime = e->xbutton.time;
156 break;
157 case KeyPress:
158 event_lasttime = e->xkey.time;
159 break;
160 case KeyRelease:
161 event_lasttime = e->xkey.time;
162 break;
163 case MotionNotify:
164 event_lasttime = e->xmotion.time;
165 break;
166 case PropertyNotify:
167 event_lasttime = e->xproperty.time;
168 break;
169 case EnterNotify:
170 case LeaveNotify:
171 event_lasttime = e->xcrossing.time;
172 break;
173 default:
174 event_lasttime = CurrentTime;
175 break;
176 }
177 }
178
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) \
184
185 static void event_hack_mods(XEvent *e)
186 {
187 KeyCode *kp;
188 int i, k;
189
190 switch (e->type) {
191 case ButtonPress:
192 case ButtonRelease:
193 STRIP_MODS(e->xbutton.state);
194 break;
195 case KeyPress:
196 STRIP_MODS(e->xkey.state);
197 break;
198 case KeyRelease:
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; */
209 i = mask_table_size;
210 break; /* get outta here! */
211 }
212 ++kp;
213 }
214 }
215 break;
216 case MotionNotify:
217 STRIP_MODS(e->xmotion.state);
218 /* compress events */
219 {
220 XEvent ce;
221 while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
222 e->type, &ce)) {
223 e->xmotion.x_root = ce.xmotion.x_root;
224 e->xmotion.y_root = ce.xmotion.y_root;
225 }
226 }
227 break;
228 }
229 }
230
231 static gboolean event_ignore(XEvent *e, Client *client)
232 {
233 switch(e->type) {
234 case FocusIn:
235 #ifdef DEBUG_FOCUS
236 g_message("FocusIn on %lx mode %d detail %d", window,
237 e->xfocus.mode, e->xfocus.detail);
238 #endif
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.
244 */
245 if (e->xfocus.detail == NotifyInferior ||
246 e->xfocus.detail > NotifyNonlinearVirtual ||
247 client == NULL) {
248 /* says a client was not found for the event (or a valid FocusIn
249 event was not found.
250 */
251 e->xfocus.window = None;
252 return TRUE;
253 }
254
255 #ifdef DEBUG_FOCUS
256 g_message("FocusIn on %lx", window);
257 #endif
258 break;
259 case FocusOut:
260 #ifdef DEBUG_FOCUS
261 g_message("FocusOut on %lx mode %d detail %d", window,
262 e->xfocus.mode, e->xfocus.detail);
263 #endif
264 if (e->xfocus.mode == NotifyGrab ||
265 e->xfocus.detail == NotifyInferior ||
266 e->xfocus.detail == NotifyAncestor ||
267 e->xfocus.detail > NotifyNonlinearVirtual) return TRUE;
268
269 #ifdef DEBUG_FOCUS
270 g_message("FocusOut on %lx", window);
271 #endif
272 /* Try process a FocusIn first, and if a legit one isn't found, then
273 do the fallback shiznit. */
274 {
275 XEvent fi, fo;
276 gboolean isfo = FALSE;
277
278 if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
279 event_process(&fi);
280
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
283 */
284 if ((isfo = XCheckTypedEvent(ob_display, FocusOut, &fo)))
285 XPutBackEvent(ob_display, &fo);
286
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)
292 return TRUE;
293 } else
294 focus_fallback(Fallback_NoFocus);
295 }
296 break;
297 case EnterNotify:
298 case LeaveNotify:
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)
303 return TRUE;
304 break;
305 }
306 return FALSE;
307 }
308
309 static void event_process(XEvent *e)
310 {
311 Window window;
312 Client *client;
313
314 window = event_get_window(e);
315 client = g_hash_table_lookup(client_map, &window);
316 event_set_lasttime(e);
317 event_hack_mods(e);
318 if (event_ignore(e, client))
319 return;
320
321 /* deal with it in the kernel */
322 if (client)
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
330 window directly */
331 XWindowChanges xwc;
332
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;
340
341 /* we are not to be held responsible if someone sends us an
342 invalid request! */
343 xerror_set_ignore(TRUE);
344 XConfigureWindow(ob_display, window,
345 e->xconfigurerequest.value_mask, &xwc);
346 xerror_set_ignore(FALSE);
347 }
348
349 /* user input (action-bound) events */
350 /*
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)
355 ;
356 */
357
358 /* dispatch the event to registered handlers */
359 dispatch_x(e, client);
360 }
361
362 static void event_handle_root(XEvent *e)
363 {
364 Atom msgtype;
365
366 switch(e->type) {
367 case ClientMessage:
368 if (e->xclient.format != 32) break;
369
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];
377 if (d > 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);
381 }
382 break;
383 case PropertyNotify:
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();
388 break;
389 }
390 }
391
392 static void event_handle_client(Client *client, XEvent *e)
393 {
394 XEvent ce;
395 Atom msgtype;
396 int i=0;
397
398 switch (e->type) {
399 case ButtonPress:
400 case ButtonRelease:
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);
405 break;
406 case Context_Close:
407 client->frame->close_press = (e->type == ButtonPress);
408 framerender_frame(client->frame);
409 break;
410 case Context_Iconify:
411 client->frame->iconify_press = (e->type == ButtonPress);
412 framerender_frame(client->frame);
413 break;
414 case Context_AllDesktops:
415 client->frame->desk_press = (e->type == ButtonPress);
416 framerender_frame(client->frame);
417 break;
418 case Context_Shade:
419 client->frame->shade_press = (e->type == ButtonPress);
420 framerender_frame(client->frame);
421 break;
422 default:
423 /* nothing changes with clicks for any other contexts */
424 break;
425 }
426 break;
427 case FocusIn:
428 focus_set_client(client);
429 case FocusOut:
430 #ifdef DEBUG_FOCUS
431 g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
432 client->window);
433 #endif
434 /* focus state can affect the stacking layer */
435 client_calc_layer(client);
436 frame_adjust_focus(client->frame);
437 break;
438 case EnterNotify:
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],
445 client);
446 focus_order[desktop] = g_list_prepend(focus_order[desktop],
447 client);
448 } else if (config_focus_follow) {
449 #ifdef DEBUG_FOCUS
450 g_message("EnterNotify on %lx, focusing window",
451 client->window);
452 #endif
453 client_focus(client);
454 }
455 }
456 break;
457 case ConfigureRequest:
458 /* compress these */
459 while (XCheckTypedWindowEvent(ob_display, client->window,
460 ConfigureRequest, &ce)) {
461 ++i;
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;
479 }
480
481 /* if we are iconic (or shaded (fvwm does this)) ignore the event */
482 if (client->iconic || client->shaded) return;
483
484 if (e->xconfigurerequest.value_mask & CWBorderWidth)
485 client->border_width = e->xconfigurerequest.border_width;
486
487 /* resize, then move, as specified in the EWMH section 7.7 */
488 if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
489 CWX | CWY)) {
490 int x, y, w, h;
491 Corner corner;
492
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;
501
502 switch (client->gravity) {
503 case NorthEastGravity:
504 case EastGravity:
505 corner = Corner_TopRight;
506 break;
507 case SouthWestGravity:
508 case SouthGravity:
509 corner = Corner_BottomLeft;
510 break;
511 case SouthEastGravity:
512 corner = Corner_BottomRight;
513 break;
514 default: /* NorthWest, Static, etc */
515 corner = Corner_TopLeft;
516 }
517
518 client_configure(client, corner, x, y, w, h, FALSE, FALSE);
519 }
520
521 if (e->xconfigurerequest.value_mask & CWStackMode) {
522 switch (e->xconfigurerequest.detail) {
523 case Below:
524 case BottomIf:
525 stacking_lower(client);
526 break;
527
528 case Above:
529 case TopIf:
530 default:
531 stacking_raise(client);
532 break;
533 }
534 }
535 break;
536 case UnmapNotify:
537 if (client->ignore_unmaps) {
538 client->ignore_unmaps--;
539 break;
540 }
541 client_unmanage(client);
542 break;
543 case DestroyNotify:
544 client_unmanage(client);
545 break;
546 case ReparentNotify:
547 /* this is when the client is first taken captive in the frame */
548 if (e->xreparent.parent == client->frame->plate) break;
549
550 /*
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.
555 */
556
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);
560
561 client_unmanage(client);
562 break;
563 case MapRequest:
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 */
572 break;
573 if (client->shaded)
574 client_shade(client, FALSE);
575 client_focus(client);
576 stacking_raise(client);
577 break;
578 case ClientMessage:
579 /* validate cuz we query stuff off the client here */
580 if (!client_validate(client)) break;
581
582 if (e->xclient.format != 32) return;
583
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);
594 break;
595 }
596 e->xclient = ce.xclient;
597 }
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);
608 break;
609 }
610 e->xclient = ce.xclient;
611 }
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],
615 FALSE);
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],
623 client->window);
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);
633 if (client->iconic)
634 client_iconify(client, FALSE, TRUE);
635 else if (!client->frame->visible)
636 /* if its not visible for other reasons, then don't mess
637 with it */
638 break;
639 if (client->shaded)
640 client_shade(client, FALSE);
641 client_focus(client);
642 stacking_raise(client);
643 }
644 break;
645 case PropertyNotify:
646 /* validate cuz we query stuff off the client here */
647 if (!client_validate(client)) break;
648
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);
656 break;
657 }
658 }
659
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);
665 }
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);
674 }
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);
686 }
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);
693 default:
694 ;
695 #ifdef SHAPE
696 if (extensions_shape && e->type == extensions_shape_event_basep) {
697 client->shaped = ((XShapeEvent*)e)->shaped;
698 frame_adjust_shape(client->frame);
699 }
700 #endif
701 }
702 }
This page took 0.072625 seconds and 5 git commands to generate.