]> Dogcows Code - chaz/openbox/blob - openbox/event.c
yet more focus fixings. RevertToPointerRoot could mess things up focusing a client...
[chaz/openbox] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "config.h"
4 #include "xerror.h"
5 #include "prop.h"
6 #include "screen.h"
7 #include "frame.h"
8 #include "engine.h"
9 #include "focus.h"
10 #include "stacking.h"
11 #include "extensions.h"
12 #include "timer.h"
13 #include "engine.h"
14 #include "dispatch.h"
15
16 #include <X11/Xlib.h>
17 #include <X11/keysym.h>
18 #include <X11/Xatom.h>
19 #ifdef HAVE_SYS_SELECT_H
20 # include <sys/select.h>
21 #endif
22
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
27 Time event_lasttime = 0;
28
29 /*! The value of the mask for the NumLock modifier */
30 unsigned int NumLockMask;
31 /*! The value of the mask for the ScrollLock modifier */
32 unsigned int ScrollLockMask;
33 /*! The key codes for the modifier keys */
34 static XModifierKeymap *modmap;
35 /*! Table of the constant modifier masks */
36 static const int mask_table[] = {
37 ShiftMask, LockMask, ControlMask, Mod1Mask,
38 Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
39 };
40 static int mask_table_size;
41
42 void event_startup()
43 {
44 mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
45
46 /* get lock masks that are defined by the display (not constant) */
47 modmap = XGetModifierMapping(ob_display);
48 g_assert(modmap);
49 if (modmap && modmap->max_keypermod > 0) {
50 size_t cnt;
51 const size_t size = mask_table_size * modmap->max_keypermod;
52 /* get the values of the keyboard lock modifiers
53 Note: Caps lock is not retrieved the same way as Scroll and Num
54 lock since it doesn't need to be. */
55 const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
56 const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
57 XK_Scroll_Lock);
58
59 for (cnt = 0; cnt < size; ++cnt) {
60 if (! modmap->modifiermap[cnt]) continue;
61
62 if (num_lock == modmap->modifiermap[cnt])
63 NumLockMask = mask_table[cnt / modmap->max_keypermod];
64 if (scroll_lock == modmap->modifiermap[cnt])
65 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
66 }
67 }
68 }
69
70 void event_shutdown()
71 {
72 XFreeModifiermap(modmap);
73 }
74
75 void event_loop()
76 {
77 fd_set selset;
78 XEvent e;
79 int x_fd;
80 struct timeval *wait;
81
82 while (TRUE) {
83 /*
84 There are slightly different event retrieval semantics here for
85 local (or high bandwidth) versus remote (or low bandwidth)
86 connections to the display/Xserver.
87 */
88 if (ob_remote) {
89 if (!XPending(ob_display))
90 break;
91 } else {
92 /*
93 This XSync allows for far more compression of events, which
94 makes things like Motion events perform far far better. Since
95 it also means network traffic for every event instead of every
96 X events (where X is the number retrieved at a time), it
97 probably should not be used for setups where Openbox is
98 running on a remote/low bandwidth display/Xserver.
99 */
100 XSync(ob_display, FALSE);
101 if (!XEventsQueued(ob_display, QueuedAlready))
102 break;
103 }
104 XNextEvent(ob_display, &e);
105
106 event_process(&e);
107 }
108
109 timer_dispatch((GTimeVal**)&wait);
110 x_fd = ConnectionNumber(ob_display);
111 FD_ZERO(&selset);
112 FD_SET(x_fd, &selset);
113 select(x_fd + 1, &selset, NULL, NULL, wait);
114 }
115
116 void event_process(XEvent *e)
117 {
118 XEvent ce;
119 KeyCode *kp;
120 Window window;
121 int i, k;
122 Client *client;
123
124 /* pick a window */
125 switch (e->type) {
126 case MapRequest:
127 window = e->xmap.window;
128 break;
129 case UnmapNotify:
130 window = e->xunmap.window;
131 break;
132 case DestroyNotify:
133 window = e->xdestroywindow.window;
134 break;
135 case ConfigureRequest:
136 window = e->xconfigurerequest.window;
137 break;
138 default:
139 #ifdef XKB
140 if (extensions_xkb && e->type == extensions_xkb_event_basep) {
141 switch (((XkbAnyEvent*)&e)->xkb_type) {
142 case XkbBellNotify:
143 window = ((XkbBellNotifyEvent*)&e)->window;
144 default:
145 window = None;
146 }
147 } else
148 #endif
149 window = e->xany.window;
150 }
151
152 client = g_hash_table_lookup(client_map, &window);
153
154 /* grab the lasttime and hack up the state */
155 switch (e->type) {
156 case ButtonPress:
157 case ButtonRelease:
158 event_lasttime = e->xbutton.time;
159 e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask);
160 /* kill off the Button1Mask etc, only want the modifiers */
161 e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask |
162 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
163 break;
164 case KeyPress:
165 event_lasttime = e->xkey.time;
166 e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
167 /* kill off the Button1Mask etc, only want the modifiers */
168 e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
169 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
170 /* add to the state the mask of the modifier being pressed, if it is
171 a modifier key being pressed (this is a little ugly..) */
172 /* I'm commenting this out cuz i don't want "C-Control_L" being returned. */
173 /* kp = modmap->modifiermap;*/
174 /* for (i = 0; i < mask_table_size; ++i) {*/
175 /* for (k = 0; k < modmap->max_keypermod; ++k) {*/
176 /* if (*kp == e->xkey.keycode) {*/ /* found the keycode */
177 /* add the mask for it */
178 /* e->xkey.state |= mask_table[i];*/
179 /* cause the first loop to break; */
180 /* i = mask_table_size;*/
181 /* break;*/ /* get outta here! */
182 /* }*/
183 /* ++kp;*/
184 /* }*/
185 /* }*/
186
187 break;
188 case KeyRelease:
189 event_lasttime = e->xkey.time;
190 e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
191 /* kill off the Button1Mask etc, only want the modifiers */
192 e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
193 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
194 /* remove from the state the mask of the modifier being released, if
195 it is a modifier key being released (this is a little ugly..) */
196 kp = modmap->modifiermap;
197 for (i = 0; i < mask_table_size; ++i) {
198 for (k = 0; k < modmap->max_keypermod; ++k) {
199 if (*kp == e->xkey.keycode) { /* found the keycode */
200 /* remove the mask for it */
201 e->xkey.state &= ~mask_table[i];
202 /* cause the first loop to break; */
203 i = mask_table_size;
204 break; /* get outta here! */
205 }
206 ++kp;
207 }
208 }
209 break;
210 case MotionNotify:
211 event_lasttime = e->xmotion.time;
212 e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask);
213 /* kill off the Button1Mask etc, only want the modifiers */
214 e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask |
215 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
216 /* compress events */
217 while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) {
218 e->xmotion.x_root = ce.xmotion.x_root;
219 e->xmotion.y_root = ce.xmotion.y_root;
220 }
221 break;
222 case PropertyNotify:
223 event_lasttime = e->xproperty.time;
224 break;
225 case FocusIn:
226 g_message("FocusIn on %lx mode %d detail %d", window,
227 e->xfocus.mode, e->xfocus.detail);
228 /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
229 because of RevertToPointerRoot. If the focus ends up reverting to
230 pointer root on a workspace change, then the FocusIn event that we
231 want will be of type NotifyAncestor. This situation does not occur
232 for FocusOut, so it is safely ignored there.
233 */
234 if (e->xfocus.detail == NotifyInferior ||
235 e->xfocus.detail > NotifyNonlinearVirtual || client == NULL) {
236 /* says a client was not found for the event (or a valid FocusIn
237 event was not found.
238 */
239 e->xfocus.window = None;
240 return;
241 }
242
243 g_message("FocusIn on %lx", window);
244 break;
245 case FocusOut:
246 g_message("FocusOut on %lx mode %d detail %d", window,
247 e->xfocus.mode, e->xfocus.detail);
248 if (e->xfocus.mode == NotifyGrab ||
249 e->xfocus.detail == NotifyInferior ||
250 e->xfocus.detail == NotifyAncestor ||
251 e->xfocus.detail > NotifyNonlinearVirtual) return;
252
253 g_message("FocusOut on %lx", window);
254 /* Try process a FocusIn first, and if a legit one isn't found, then
255 do the fallback shiznit. */
256 {
257 XEvent fi;
258 if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
259 event_process(&fi);
260
261 /* secret magic way of event_process telling us that no client
262 was found for the FocusIn event. ^_^ */
263 if (fi.xfocus.window == None)
264 focus_fallback(FALSE);
265 if (fi.xfocus.window == e->xfocus.window)
266 return;
267 } else
268 focus_fallback(FALSE);
269 }
270 break;
271 case EnterNotify:
272 case LeaveNotify:
273 event_lasttime = e->xcrossing.time;
274 /* NotifyUngrab occurs when a mouse button is released and the event is
275 caused, like when lowering a window */
276 if (e->xcrossing.mode == NotifyGrab) return;
277 break;
278 default:
279 event_lasttime = CurrentTime;
280 break;
281 }
282
283 /* deal with it in the kernel */
284 if (client)
285 event_handle_client(client, e);
286 else if (window == ob_root)
287 event_handle_root(e);
288 else if (e->type == MapRequest)
289 client_manage(window);
290 else if (e->type == ConfigureRequest) {
291 /* unhandled configure requests must be used to configure the
292 window directly */
293 XWindowChanges xwc;
294
295 xwc.x = e->xconfigurerequest.x;
296 xwc.y = e->xconfigurerequest.y;
297 xwc.width = e->xconfigurerequest.width;
298 xwc.height = e->xconfigurerequest.height;
299 xwc.border_width = e->xconfigurerequest.border_width;
300 xwc.sibling = e->xconfigurerequest.above;
301 xwc.stack_mode = e->xconfigurerequest.detail;
302
303 /* we are not to be held responsible if someone sends us an
304 invalid request! */
305 xerror_set_ignore(TRUE);
306 XConfigureWindow(ob_display, window,
307 e->xconfigurerequest.value_mask, &xwc);
308 xerror_set_ignore(FALSE);
309 }
310
311 /* dispatch the event to registered handlers */
312 dispatch_x(e, client);
313 }
314
315 static void event_handle_root(XEvent *e)
316 {
317 Atom msgtype;
318
319 switch(e->type) {
320 case ClientMessage:
321 if (e->xclient.format != 32) break;
322
323 msgtype = e->xclient.message_type;
324 if (msgtype == prop_atoms.net_current_desktop) {
325 unsigned int d = e->xclient.data.l[0];
326 if (d < screen_num_desktops)
327 screen_set_desktop(d);
328 } else if (msgtype == prop_atoms.net_number_of_desktops) {
329 unsigned int d = e->xclient.data.l[0];
330 if (d > 0)
331 screen_set_num_desktops(d);
332 } else if (msgtype == prop_atoms.net_showing_desktop) {
333 screen_show_desktop(e->xclient.data.l[0] != 0);
334 }
335 break;
336 case PropertyNotify:
337 if (e->xproperty.atom == prop_atoms.net_desktop_names)
338 screen_update_desktop_names();
339 else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
340 screen_update_layout();
341 break;
342 }
343 }
344
345 static void event_handle_client(Client *client, XEvent *e)
346 {
347 XEvent ce;
348 Atom msgtype;
349 int i=0;
350 ConfigValue focus_follow;
351
352 switch (e->type) {
353 case FocusIn:
354 focus_set_client(client);
355 case FocusOut:
356 g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
357 client->window);
358 /* focus state can affect the stacking layer */
359 client_calc_layer(client);
360 engine_frame_adjust_focus(client->frame);
361 break;
362 case EnterNotify:
363 if (client_normal(client)) {
364 if (ob_state == State_Starting) {
365 /* move it to the top of the focus order */
366 guint desktop = client->desktop;
367 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
368 focus_order[desktop] = g_list_remove(focus_order[desktop],
369 client);
370 focus_order[desktop] = g_list_prepend(focus_order[desktop],
371 client);
372 } else {
373 if (!config_get("focusFollowsMouse",Config_Bool,&focus_follow))
374 g_assert_not_reached();
375 if (focus_follow.bool)
376 client_focus(client);
377 }
378 }
379 break;
380 case ConfigureRequest:
381 /* compress these */
382 while (XCheckTypedWindowEvent(ob_display, client->window,
383 ConfigureRequest, &ce)) {
384 ++i;
385 /* XXX if this causes bad things.. we can compress config req's
386 with the same mask. */
387 e->xconfigurerequest.value_mask |=
388 ce.xconfigurerequest.value_mask;
389 if (ce.xconfigurerequest.value_mask & CWX)
390 e->xconfigurerequest.x = ce.xconfigurerequest.x;
391 if (ce.xconfigurerequest.value_mask & CWY)
392 e->xconfigurerequest.y = ce.xconfigurerequest.y;
393 if (ce.xconfigurerequest.value_mask & CWWidth)
394 e->xconfigurerequest.width = ce.xconfigurerequest.width;
395 if (ce.xconfigurerequest.value_mask & CWHeight)
396 e->xconfigurerequest.height = ce.xconfigurerequest.height;
397 if (ce.xconfigurerequest.value_mask & CWBorderWidth)
398 e->xconfigurerequest.border_width =
399 ce.xconfigurerequest.border_width;
400 if (ce.xconfigurerequest.value_mask & CWStackMode)
401 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
402 }
403 if (i) g_message("Compressed %d Configures", i);
404
405 /* if we are iconic (or shaded (fvwm does this)) ignore the event */
406 if (client->iconic || client->shaded) return;
407
408 if (e->xconfigurerequest.value_mask & CWBorderWidth)
409 client->border_width = e->xconfigurerequest.border_width;
410
411 /* resize, then move, as specified in the EWMH section 7.7 */
412 if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
413 CWX | CWY)) {
414 int x, y, w, h;
415 Corner corner;
416
417 x = (e->xconfigurerequest.value_mask & CWX) ?
418 e->xconfigurerequest.x : client->area.x;
419 y = (e->xconfigurerequest.value_mask & CWY) ?
420 e->xconfigurerequest.y : client->area.y;
421 w = (e->xconfigurerequest.value_mask & CWWidth) ?
422 e->xconfigurerequest.width : client->area.width;
423 h = (e->xconfigurerequest.value_mask & CWHeight) ?
424 e->xconfigurerequest.height : client->area.height;
425
426 switch (client->gravity) {
427 case NorthEastGravity:
428 case EastGravity:
429 corner = Corner_TopRight;
430 break;
431 case SouthWestGravity:
432 case SouthGravity:
433 corner = Corner_BottomLeft;
434 break;
435 case SouthEastGravity:
436 corner = Corner_BottomRight;
437 break;
438 default: /* NorthWest, Static, etc */
439 corner = Corner_TopLeft;
440 }
441
442 client_configure(client, corner, x, y, w, h, FALSE, FALSE);
443 }
444
445 if (e->xconfigurerequest.value_mask & CWStackMode) {
446 switch (e->xconfigurerequest.detail) {
447 case Below:
448 case BottomIf:
449 stacking_lower(client);
450 break;
451
452 case Above:
453 case TopIf:
454 default:
455 stacking_raise(client);
456 break;
457 }
458 }
459 break;
460 case UnmapNotify:
461 if (client->ignore_unmaps) {
462 client->ignore_unmaps--;
463 break;
464 }
465 client_unmanage(client);
466 break;
467 case DestroyNotify:
468 client_unmanage(client);
469 break;
470 case ReparentNotify:
471 /* this is when the client is first taken captive in the frame */
472 if (e->xreparent.parent == client->frame->plate) break;
473
474 /*
475 This event is quite rare and is usually handled in unmapHandler.
476 However, if the window is unmapped when the reparent event occurs,
477 the window manager never sees it because an unmap event is not sent
478 to an already unmapped window.
479 */
480
481 /* we don't want the reparent event, put it back on the stack for the
482 X server to deal with after we unmanage the window */
483 XPutBackEvent(ob_display, e);
484
485 client_unmanage(client);
486 break;
487 case MapRequest:
488 if (!client->iconic) break; /* this normally doesn't happen, but if it
489 does, we don't want it! */
490 if (screen_showing_desktop)
491 screen_show_desktop(FALSE);
492 client_iconify(client, FALSE, TRUE);
493 if (!client->frame->visible)
494 /* if its not visible still, then don't mess with it */
495 break;
496 if (client->shaded)
497 client_shade(client, FALSE);
498 client_focus(client);
499 stacking_raise(client);
500 break;
501 case ClientMessage:
502 /* validate cuz we query stuff off the client here */
503 if (!client_validate(client)) break;
504
505 if (e->xclient.format != 32) return;
506
507 msgtype = e->xclient.message_type;
508 if (msgtype == prop_atoms.wm_change_state) {
509 /* compress changes into a single change */
510 while (XCheckTypedWindowEvent(ob_display, e->type,
511 client->window, &ce)) {
512 /* XXX: it would be nice to compress ALL messages of a
513 type, not just messages in a row without other
514 message types between. */
515 if (ce.xclient.message_type != msgtype) {
516 XPutBackEvent(ob_display, &ce);
517 break;
518 }
519 e->xclient = ce.xclient;
520 }
521 client_set_wm_state(client, e->xclient.data.l[0]);
522 } else if (msgtype == prop_atoms.net_wm_desktop) {
523 /* compress changes into a single change */
524 while (XCheckTypedWindowEvent(ob_display, e->type,
525 client->window, &ce)) {
526 /* XXX: it would be nice to compress ALL messages of a
527 type, not just messages in a row without other
528 message types between. */
529 if (ce.xclient.message_type != msgtype) {
530 XPutBackEvent(ob_display, &ce);
531 break;
532 }
533 e->xclient = ce.xclient;
534 }
535 if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
536 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
537 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
538 FALSE);
539 } else if (msgtype == prop_atoms.net_wm_state) {
540 /* can't compress these */
541 g_message("net_wm_state %s %ld %ld for 0x%lx",
542 (e->xclient.data.l[0] == 0 ? "Remove" :
543 e->xclient.data.l[0] == 1 ? "Add" :
544 e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
545 e->xclient.data.l[1], e->xclient.data.l[2],
546 client->window);
547 client_set_state(client, e->xclient.data.l[0],
548 e->xclient.data.l[1], e->xclient.data.l[2]);
549 } else if (msgtype == prop_atoms.net_close_window) {
550 g_message("net_close_window for 0x%lx", client->window);
551 client_close(client);
552 } else if (msgtype == prop_atoms.net_active_window) {
553 g_message("net_active_window for 0x%lx", client->window);
554 if (screen_showing_desktop)
555 screen_show_desktop(FALSE);
556 if (client->iconic)
557 client_iconify(client, FALSE, TRUE);
558 else if (!client->frame->visible)
559 /* if its not visible for other reasons, then don't mess
560 with it */
561 break;
562 if (client->shaded)
563 client_shade(client, FALSE);
564 client_focus(client);
565 stacking_raise(client);
566 }
567 break;
568 case PropertyNotify:
569 /* validate cuz we query stuff off the client here */
570 if (!client_validate(client)) break;
571
572 /* compress changes to a single property into a single change */
573 while (XCheckTypedWindowEvent(ob_display, e->type,
574 client->window, &ce)) {
575 /* XXX: it would be nice to compress ALL changes to a property,
576 not just changes in a row without other props between. */
577 if (ce.xproperty.atom != e->xproperty.atom) {
578 XPutBackEvent(ob_display, &ce);
579 break;
580 }
581 }
582
583 msgtype = e->xproperty.atom;
584 if (msgtype == XA_WM_NORMAL_HINTS) {
585 client_update_normal_hints(client);
586 /* normal hints can make a window non-resizable */
587 client_setup_decor_and_functions(client);
588 }
589 else if (msgtype == XA_WM_HINTS)
590 client_update_wmhints(client);
591 else if (msgtype == XA_WM_TRANSIENT_FOR) {
592 client_update_transient_for(client);
593 client_get_type(client);
594 /* type may have changed, so update the layer */
595 client_calc_layer(client);
596 client_setup_decor_and_functions(client);
597 }
598 else if (msgtype == prop_atoms.net_wm_name ||
599 msgtype == prop_atoms.wm_name)
600 client_update_title(client);
601 else if (msgtype == prop_atoms.net_wm_icon_name ||
602 msgtype == prop_atoms.wm_icon_name)
603 client_update_icon_title(client);
604 else if (msgtype == prop_atoms.wm_class)
605 client_update_class(client);
606 else if (msgtype == prop_atoms.wm_protocols) {
607 client_update_protocols(client);
608 client_setup_decor_and_functions(client);
609 }
610 else if (msgtype == prop_atoms.net_wm_strut)
611 client_update_strut(client);
612 else if (msgtype == prop_atoms.net_wm_icon)
613 client_update_icons(client);
614 else if (msgtype == prop_atoms.kwm_win_icon)
615 client_update_kwm_icon(client);
616 default:
617 ;
618 #ifdef SHAPE
619 if (extensions_shape && e->type == extensions_shape_event_basep) {
620 client->shaped = ((XShapeEvent*)e)->shaped;
621 engine_frame_adjust_shape(client->frame);
622 }
623 #endif
624 }
625 }
This page took 0.063118 seconds and 5 git commands to generate.