]> Dogcows Code - chaz/openbox/blob - openbox/event.c
give engines mouse event notifications. make the openbox engine display pressed buttons
[chaz/openbox] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "xerror.h"
4 #include "prop.h"
5 #include "screen.h"
6 #include "frame.h"
7 #include "engine.h"
8 #include "focus.h"
9 #include "stacking.h"
10 #include "keyboard.h"
11 #include "pointer.h"
12 #include "hooks.h"
13 #include "extensions.h"
14 #include "timer.h"
15 #include "engine.h"
16
17 #include <X11/Xlib.h>
18 #include <X11/keysym.h>
19 #include <X11/Xatom.h>
20
21 static void event_process(XEvent *e);
22 static void event_handle_root(XEvent *e);
23 static void event_handle_client(Client *c, XEvent *e);
24
25 Time event_lasttime = 0;
26
27 /*! A list of all possible combinations of keyboard lock masks */
28 static unsigned int mask_list[8];
29 /*! The value of the mask for the NumLock modifier */
30 static unsigned int NumLockMask;
31 /*! The value of the mask for the ScrollLock modifier */
32 static 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 mask_list[0] = 0;
70 mask_list[1] = LockMask;
71 mask_list[2] = NumLockMask;
72 mask_list[3] = LockMask | NumLockMask;
73 mask_list[4] = ScrollLockMask;
74 mask_list[5] = ScrollLockMask | LockMask;
75 mask_list[6] = ScrollLockMask | NumLockMask;
76 mask_list[7] = ScrollLockMask | LockMask | NumLockMask;
77 }
78
79 void event_shutdown()
80 {
81 XFreeModifiermap(modmap);
82 }
83
84 void event_loop()
85 {
86 fd_set selset;
87 XEvent e;
88 int x_fd;
89 struct timeval *wait;
90
91 while (TRUE) {
92 /*
93 There are slightly different event retrieval semantics here for
94 local (or high bandwidth) versus remote (or low bandwidth)
95 connections to the display/Xserver.
96 */
97 if (ob_remote) {
98 if (!XPending(ob_display))
99 break;
100 } else {
101 /*
102 This XSync allows for far more compression of events, which
103 makes things like Motion events perform far far better. Since
104 it also means network traffic for every event instead of every
105 X events (where X is the number retrieved at a time), it
106 probably should not be used for setups where Openbox is
107 running on a remote/low bandwidth display/Xserver.
108 */
109 XSync(ob_display, FALSE);
110 if (!XEventsQueued(ob_display, QueuedAlready))
111 break;
112 }
113 XNextEvent(ob_display, &e);
114
115 event_process(&e);
116 }
117
118 timer_dispatch((GTimeVal**)&wait);
119 x_fd = ConnectionNumber(ob_display);
120 FD_ZERO(&selset);
121 FD_SET(x_fd, &selset);
122 select(x_fd + 1, &selset, NULL, NULL, wait);
123 }
124
125 void event_process(XEvent *e)
126 {
127 XEvent ce;
128 KeyCode *kp;
129 Window window;
130 int i, k;
131 Client *client;
132
133 /* pick a window */
134 switch (e->type) {
135 case UnmapNotify:
136 window = e->xunmap.window;
137 break;
138 case DestroyNotify:
139 window = e->xdestroywindow.window;
140 break;
141 case ConfigureRequest:
142 window = e->xconfigurerequest.window;
143 break;
144 default:
145 /* XKB events */
146 if (e->type == extensions_xkb_event_basep) {
147 switch (((XkbAnyEvent*)&e)->xkb_type) {
148 case XkbBellNotify:
149 window = ((XkbBellNotifyEvent*)&e)->window;
150 default:
151 window = None;
152 }
153 } else
154 window = e->xany.window;
155 }
156
157 /* grab the lasttime and hack up the state */
158 switch (e->type) {
159 case ButtonPress:
160 case ButtonRelease:
161 event_lasttime = e->xbutton.time;
162 e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask);
163 /* kill off the Button1Mask etc, only want the modifiers */
164 e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask |
165 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
166 break;
167 case KeyPress:
168 event_lasttime = e->xkey.time;
169 e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
170 /* kill off the Button1Mask etc, only want the modifiers */
171 e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
172 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
173 /* add to the state the mask of the modifier being pressed, if it is
174 a modifier key being pressed (this is a little ugly..) */
175 /* I'm commenting this out cuz i don't want "C-Control_L" being returned. */
176 /* kp = modmap->modifiermap;*/
177 /* for (i = 0; i < mask_table_size; ++i) {*/
178 /* for (k = 0; k < modmap->max_keypermod; ++k) {*/
179 /* if (*kp == e->xkey.keycode) {*/ /* found the keycode */
180 /* add the mask for it */
181 /* e->xkey.state |= mask_table[i];*/
182 /* cause the first loop to break; */
183 /* i = mask_table_size;*/
184 /* break;*/ /* get outta here! */
185 /* }*/
186 /* ++kp;*/
187 /* }*/
188 /* }*/
189
190 break;
191 case KeyRelease:
192 event_lasttime = e->xkey.time;
193 e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
194 /* kill off the Button1Mask etc, only want the modifiers */
195 e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
196 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
197 /* remove from the state the mask of the modifier being released, if
198 it is a modifier key being released (this is a little ugly..) */
199 kp = modmap->modifiermap;
200 for (i = 0; i < mask_table_size; ++i) {
201 for (k = 0; k < modmap->max_keypermod; ++k) {
202 if (*kp == e->xkey.keycode) { /* found the keycode */
203 /* remove the mask for it */
204 e->xkey.state &= ~mask_table[i];
205 /* cause the first loop to break; */
206 i = mask_table_size;
207 break; /* get outta here! */
208 }
209 ++kp;
210 }
211 }
212 break;
213 case MotionNotify:
214 event_lasttime = e->xmotion.time;
215 e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask);
216 /* kill off the Button1Mask etc, only want the modifiers */
217 e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask |
218 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
219 /* compress events */
220 while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) {
221 e->xmotion.x_root = ce.xmotion.x_root;
222 e->xmotion.y_root = ce.xmotion.y_root;
223 }
224 break;
225 case PropertyNotify:
226 event_lasttime = e->xproperty.time;
227 break;
228 case FocusIn:
229 case FocusOut:
230 if (e->xfocus.mode == NotifyGrab)
231 /*|| e.xfocus.mode == NotifyUngrab ||*/
232
233 /* From Metacity, from WindowMaker, ignore all funky pointer
234 root events. Its commented out cuz I don't think we need this
235 at all. If problems arise we can look into it */
236 /*e.xfocus.detail > NotifyNonlinearVirtual) */
237 return; /* skip me! */
238 if (e->type == FocusOut) {
239 /* FocusOut events just make us look for FocusIn events. They
240 are mostly ignored otherwise. */
241 XEvent fi;
242 if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
243 event_process(&fi);
244 /* dont unfocus the window we just focused! */
245 if (fi.xfocus.window == e->xfocus.window)
246 return;
247 }
248 }
249 break;
250 case EnterNotify:
251 case LeaveNotify:
252 event_lasttime = e->xcrossing.time;
253 if (e->xcrossing.mode != NotifyNormal)
254 return; /* skip me! */
255 break;
256 }
257
258 client = g_hash_table_lookup(client_map, (gpointer)window);
259
260 if (client) {
261 event_handle_client(client, e);
262 } else if (window == ob_root)
263 event_handle_root(e);
264 else if (e->type == ConfigureRequest) {
265 /* unhandled configure requests must be used to configure the
266 window directly */
267 XWindowChanges xwc;
268
269 xwc.x = e->xconfigurerequest.x;
270 xwc.y = e->xconfigurerequest.y;
271 xwc.width = e->xconfigurerequest.width;
272 xwc.height = e->xconfigurerequest.height;
273 xwc.border_width = e->xconfigurerequest.border_width;
274 xwc.sibling = e->xconfigurerequest.above;
275 xwc.stack_mode = e->xconfigurerequest.detail;
276
277 g_message("Proxying configure event for 0x%lx", window);
278
279 /* we are not to be held responsible if someone sends us an
280 invalid request! */
281 xerror_set_ignore(TRUE);
282 XConfigureWindow(ob_display, window,
283 e->xconfigurerequest.value_mask, &xwc);
284 xerror_set_ignore(FALSE);
285 }
286
287 /* dispatch Crossing, Pointer and Key events to the hooks */
288 switch(e->type) {
289 case EnterNotify:
290 if (client != NULL) engine_mouse_enter(client->frame, window);
291 HOOKFIRECLIENT(pointerenter, client);
292 break;
293 case LeaveNotify:
294 if (client != NULL) engine_mouse_leave(client->frame, window);
295 HOOKFIRECLIENT(pointerleave, client);
296 break;
297 case ButtonPress:
298 if (client != NULL)
299 engine_mouse_press(client->frame, window,
300 e->xbutton.x, e->xbutton.y);
301 pointer_event(e, client);
302 break;
303 case ButtonRelease:
304 if (client != NULL)
305 engine_mouse_release(client->frame, window,
306 e->xbutton.x, e->xbutton.y);
307 pointer_event(e, client);
308 break;
309 case MotionNotify:
310 pointer_event(e, client);
311 break;
312 case KeyPress:
313 case KeyRelease:
314 keyboard_event(&e->xkey);
315 break;
316 default:
317 /* XKB events */
318 if (e->type == extensions_xkb_event_basep) {
319 switch (((XkbAnyEvent*)&e)->xkb_type) {
320 case XkbBellNotify:
321 HOOKFIRECLIENT(bell, client);
322 break;
323 }
324 }
325 }
326 }
327
328 static void event_handle_root(XEvent *e)
329 {
330 Atom msgtype;
331
332 switch(e->type) {
333 case MapRequest:
334 g_message("MapRequest on root");
335 client_manage(e->xmap.window);
336 break;
337 case ClientMessage:
338 if (e->xclient.format != 32) break;
339
340 msgtype = e->xclient.message_type;
341 if (msgtype == prop_atoms.net_current_desktop) {
342 unsigned int d = e->xclient.data.l[0];
343 if (d <= screen_num_desktops)
344 screen_set_desktop(d);
345 } else if (msgtype == prop_atoms.net_number_of_desktops) {
346 unsigned int d = e->xclient.data.l[0];
347 if (d > 0)
348 screen_set_num_desktops(d);
349 } else if (msgtype == prop_atoms.net_showing_desktop) {
350 screen_show_desktop(e->xclient.data.l[0] != 0);
351 }
352 break;
353 case PropertyNotify:
354 if (e->xproperty.atom == prop_atoms.net_desktop_names)
355 screen_update_desktop_names();
356 else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
357 screen_update_layout();
358 break;
359 }
360 }
361
362 static void event_handle_client(Client *client, XEvent *e)
363 {
364 XEvent ce;
365 Atom msgtype;
366
367 switch (e->type) {
368 case FocusIn:
369 client->focused = TRUE;
370 engine_frame_adjust_focus(client->frame);
371
372 /* focus state can affect the stacking layer */
373 client_calc_layer(client);
374
375 focus_set_client(client);
376 break;
377 case FocusOut:
378 client->focused = FALSE;
379 engine_frame_adjust_focus(client->frame);
380
381 /* focus state can affect the stacking layer */
382 client_calc_layer(client);
383
384 if (focus_client == client)
385 focus_set_client(NULL);
386 break;
387 case ConfigureRequest:
388 g_message("ConfigureRequest for window %lx", client->window);
389 /* compress these */
390 while (XCheckTypedWindowEvent(ob_display, client->window,
391 ConfigureRequest, &ce)) {
392 /* XXX if this causes bad things.. we can compress config req's
393 with the same mask. */
394 e->xconfigurerequest.value_mask |=
395 ce.xconfigurerequest.value_mask;
396 if (ce.xconfigurerequest.value_mask & CWX)
397 e->xconfigurerequest.x = ce.xconfigurerequest.x;
398 if (ce.xconfigurerequest.value_mask & CWY)
399 e->xconfigurerequest.y = ce.xconfigurerequest.y;
400 if (ce.xconfigurerequest.value_mask & CWWidth)
401 e->xconfigurerequest.width = ce.xconfigurerequest.width;
402 if (ce.xconfigurerequest.value_mask & CWHeight)
403 e->xconfigurerequest.height = ce.xconfigurerequest.height;
404 if (ce.xconfigurerequest.value_mask & CWBorderWidth)
405 e->xconfigurerequest.border_width =
406 ce.xconfigurerequest.border_width;
407 if (ce.xconfigurerequest.value_mask & CWStackMode)
408 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
409 }
410
411 /* if we are iconic (or shaded (fvwm does this)) ignore the event */
412 if (client->iconic || client->shaded) return;
413
414 if (e->xconfigurerequest.value_mask & CWBorderWidth)
415 client->border_width = e->xconfigurerequest.border_width;
416
417 /* resize, then move, as specified in the EWMH section 7.7 */
418 if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
419 CWX | CWY)) {
420 int x, y, w, h;
421 Corner corner;
422
423 x = (e->xconfigurerequest.value_mask & CWX) ?
424 e->xconfigurerequest.x : client->area.x;
425 y = (e->xconfigurerequest.value_mask & CWY) ?
426 e->xconfigurerequest.y : client->area.y;
427 w = (e->xconfigurerequest.value_mask & CWWidth) ?
428 e->xconfigurerequest.width : client->area.width;
429 h = (e->xconfigurerequest.value_mask & CWHeight) ?
430 e->xconfigurerequest.height : client->area.height;
431
432 switch (client->gravity) {
433 case NorthEastGravity:
434 case EastGravity:
435 corner = Corner_TopRight;
436 break;
437 case SouthWestGravity:
438 case SouthGravity:
439 corner = Corner_BottomLeft;
440 break;
441 case SouthEastGravity:
442 corner = Corner_BottomRight;
443 break;
444 default: /* NorthWest, Static, etc */
445 corner = Corner_TopLeft;
446 }
447
448 client_configure(client, corner, x, y, w, h, FALSE, FALSE);
449 }
450
451 if (e->xconfigurerequest.value_mask & CWStackMode) {
452 switch (e->xconfigurerequest.detail) {
453 case Below:
454 case BottomIf:
455 stacking_lower(client);
456 break;
457
458 case Above:
459 case TopIf:
460 default:
461 stacking_raise(client);
462 break;
463 }
464 }
465 break;
466 case UnmapNotify:
467 if (client->ignore_unmaps) {
468 client->ignore_unmaps--;
469 break;
470 }
471 g_message("UnmapNotify for %lx", client->window);
472 client_unmanage(client);
473 break;
474 case DestroyNotify:
475 g_message("DestroyNotify for %lx", client->window);
476 client_unmanage(client);
477 break;
478 case ReparentNotify:
479 /* this is when the client is first taken captive in the frame */
480 if (e->xreparent.parent == client->frame->plate) break;
481
482 /*
483 This event is quite rare and is usually handled in unmapHandler.
484 However, if the window is unmapped when the reparent event occurs,
485 the window manager never sees it because an unmap event is not sent
486 to an already unmapped window.
487 */
488
489 /* we don't want the reparent event, put it back on the stack for the
490 X server to deal with after we unmanage the window */
491 XPutBackEvent(ob_display, e);
492
493 client_unmanage(client);
494 break;
495 case MapRequest:
496 /* we shouldn't be able to get this unless we're iconic */
497 g_assert(client->iconic);
498
499 HOOKFIRECLIENT(requestactivate, 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 client_set_desktop(client, e->xclient.data.l[0]);
536 } else if (msgtype == prop_atoms.net_wm_state) {
537 /* can't compress these */
538 g_message("net_wm_state %s %ld %ld for 0x%lx\n",
539 (e->xclient.data.l[0] == 0 ? "Remove" :
540 e->xclient.data.l[0] == 1 ? "Add" :
541 e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
542 e->xclient.data.l[1], e->xclient.data.l[2],
543 client->window);
544 client_set_state(client, e->xclient.data.l[0],
545 e->xclient.data.l[1], e->xclient.data.l[2]);
546 } else if (msgtype == prop_atoms.net_close_window) {
547 g_message("net_close_window for 0x%lx\n", client->window);
548 client_close(client);
549 } else if (msgtype == prop_atoms.net_active_window) {
550 g_message("net_active_window for 0x%lx\n", client->window);
551 if (screen_showing_desktop)
552 screen_show_desktop(FALSE);
553 if (client->iconic)
554 client_iconify(client, FALSE, TRUE);
555 else if (!client->frame->visible)
556 /* if its not visible for other reasons, then don't mess
557 with it */
558 return;
559 HOOKFIRECLIENT(requestactivate, client);
560 }
561 break;
562 case PropertyNotify:
563 /* validate cuz we query stuff off the client here */
564 if (!client_validate(client)) break;
565
566 /* compress changes to a single property into a single change */
567 while (XCheckTypedWindowEvent(ob_display, e->type,
568 client->window, &ce)) {
569 /* XXX: it would be nice to compress ALL changes to a property,
570 not just changes in a row without other props between. */
571 if (ce.xproperty.atom != e->xproperty.atom) {
572 XPutBackEvent(ob_display, &ce);
573 break;
574 }
575 }
576
577 msgtype = e->xproperty.atom;
578 if (msgtype == XA_WM_NORMAL_HINTS) {
579 client_update_normal_hints(client);
580 /* normal hints can make a window non-resizable */
581 client_setup_decor_and_functions(client);
582 }
583 else if (msgtype == XA_WM_HINTS)
584 client_update_wmhints(client);
585 else if (msgtype == XA_WM_TRANSIENT_FOR) {
586 client_update_transient_for(client);
587 client_get_type(client);
588 /* type may have changed, so update the layer */
589 client_calc_layer(client);
590 client_setup_decor_and_functions(client);
591 }
592 else if (msgtype == prop_atoms.net_wm_name ||
593 msgtype == prop_atoms.wm_name)
594 client_update_title(client);
595 else if (msgtype == prop_atoms.net_wm_icon_name ||
596 msgtype == prop_atoms.wm_icon_name)
597 client_update_icon_title(client);
598 else if (msgtype == prop_atoms.wm_class)
599 client_update_class(client);
600 else if (msgtype == prop_atoms.wm_protocols) {
601 client_update_protocols(client);
602 client_setup_decor_and_functions(client);
603 }
604 else if (msgtype == prop_atoms.net_wm_strut)
605 client_update_strut(client);
606 else if (msgtype == prop_atoms.net_wm_icon)
607 client_update_icons(client);
608 else if (msgtype == prop_atoms.kwm_win_icon)
609 client_update_kwm_icon(client);
610 }
611 }
This page took 0.064177 seconds and 5 git commands to generate.