not using CurrentTime anywhere
[mikachu/openbox.git] / 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 "extensions.h"
11 #include "timer.h"
12 #include "engine.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 Time event_unfocustime = 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     /* grab the lasttime and hack up the state */
153     switch (e->type) {
154     case ButtonPress:
155     case ButtonRelease:
156         event_lasttime = e->xbutton.time;
157         e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask);
158         /* kill off the Button1Mask etc, only want the modifiers */
159         e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask |
160                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
161         break;
162     case KeyPress:
163         event_lasttime = e->xkey.time;
164         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
165         /* kill off the Button1Mask etc, only want the modifiers */
166         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
167                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
168         /* add to the state the mask of the modifier being pressed, if it is
169            a modifier key being pressed (this is a little ugly..) */
170 /* I'm commenting this out cuz i don't want "C-Control_L" being returned. */
171 /*      kp = modmap->modifiermap;*/
172 /*      for (i = 0; i < mask_table_size; ++i) {*/
173 /*          for (k = 0; k < modmap->max_keypermod; ++k) {*/
174 /*              if (*kp == e->xkey.keycode) {*/ /* found the keycode */
175                     /* add the mask for it */
176 /*                  e->xkey.state |= mask_table[i];*/
177                     /* cause the first loop to break; */
178 /*                  i = mask_table_size;*/
179 /*                  break;*/ /* get outta here! */
180 /*              }*/
181 /*              ++kp;*/
182 /*          }*/
183 /*      }*/
184
185         break;
186     case KeyRelease:
187         event_lasttime = e->xkey.time;
188         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
189         /* kill off the Button1Mask etc, only want the modifiers */
190         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
191                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
192         /* remove from the state the mask of the modifier being released, if
193            it is a modifier key being released (this is a little ugly..) */
194         kp = modmap->modifiermap;
195         for (i = 0; i < mask_table_size; ++i) {
196             for (k = 0; k < modmap->max_keypermod; ++k) {
197                 if (*kp == e->xkey.keycode) { /* found the keycode */
198                     /* remove the mask for it */
199                     e->xkey.state &= ~mask_table[i];
200                     /* cause the first loop to break; */
201                     i = mask_table_size;
202                     break; /* get outta here! */
203                 }
204                 ++kp;
205             }
206         }
207         break;
208     case MotionNotify:
209         event_lasttime = e->xmotion.time;
210         e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask);
211         /* kill off the Button1Mask etc, only want the modifiers */
212         e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask |
213                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
214         /* compress events */
215         while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) {
216             e->xmotion.x_root = ce.xmotion.x_root;
217             e->xmotion.y_root = ce.xmotion.y_root;
218         }
219         break;
220     case PropertyNotify:
221         event_lasttime = e->xproperty.time;
222         break;
223     case FocusIn:
224         if (e->xfocus.mode == NotifyGrab ||
225             !(e->xfocus.detail == NotifyNonlinearVirtual ||
226               e->xfocus.detail == NotifyNonlinear))
227             return;
228         break;
229     case FocusOut:
230         if (e->xfocus.mode == NotifyGrab ||
231             !(e->xfocus.detail == NotifyNonlinearVirtual ||
232               e->xfocus.detail == NotifyNonlinear))
233             return;
234
235         /* FocusOut events just make us look for FocusIn events. They
236            are mostly ignored otherwise. */
237         {
238             XEvent fi;
239             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
240                 event_process(&fi);
241
242                 if (fi.xfocus.window == e->xfocus.window)
243                     return;
244             }
245         }
246         break;
247     case EnterNotify:
248     case LeaveNotify:
249         event_lasttime = e->xcrossing.time;
250         /* NotifyUngrab occurs when a mouse button is released and the event is
251            caused, like when lowering a window */
252         if (e->xcrossing.mode == NotifyGrab) return;
253         break;
254     }
255
256     client = g_hash_table_lookup(client_map, &window);
257
258     /* deal with it in the kernel */
259     if (client)
260         event_handle_client(client, e);
261     else if (window == ob_root)
262         event_handle_root(e);
263     else if (e->type == MapRequest)
264         client_manage(window);
265     else if (e->type == ConfigureRequest) {
266         /* unhandled configure requests must be used to configure the
267            window directly */
268         XWindowChanges xwc;
269                
270         xwc.x = e->xconfigurerequest.x;
271         xwc.y = e->xconfigurerequest.y;
272         xwc.width = e->xconfigurerequest.width;
273         xwc.height = e->xconfigurerequest.height;
274         xwc.border_width = e->xconfigurerequest.border_width;
275         xwc.sibling = e->xconfigurerequest.above;
276         xwc.stack_mode = e->xconfigurerequest.detail;
277        
278         /* we are not to be held responsible if someone sends us an
279            invalid request! */
280         xerror_set_ignore(TRUE);
281         XConfigureWindow(ob_display, window,
282                          e->xconfigurerequest.value_mask, &xwc);
283         xerror_set_ignore(FALSE);
284     }
285
286     /* dispatch the event to registered handlers */
287     dispatch_x(e, client);
288 }
289
290 static void event_handle_root(XEvent *e)
291 {
292     Atom msgtype;
293      
294     switch(e->type) {
295     case ClientMessage:
296         if (e->xclient.format != 32) break;
297
298         msgtype = e->xclient.message_type;
299         if (msgtype == prop_atoms.net_current_desktop) {
300             unsigned int d = e->xclient.data.l[0];
301             if (d < screen_num_desktops)
302                 screen_set_desktop(d);
303         } else if (msgtype == prop_atoms.net_number_of_desktops) {
304             unsigned int d = e->xclient.data.l[0];
305             if (d > 0)
306                 screen_set_num_desktops(d);
307         } else if (msgtype == prop_atoms.net_showing_desktop) {
308             screen_show_desktop(e->xclient.data.l[0] != 0);
309         }
310         break;
311     case PropertyNotify:
312         if (e->xproperty.atom == prop_atoms.net_desktop_names)
313             screen_update_desktop_names();
314         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
315             screen_update_layout();
316         break;
317     }
318 }
319
320 static void event_handle_client(Client *client, XEvent *e)
321 {
322     XEvent ce;
323     Atom msgtype;
324         int i=0;
325      
326     switch (e->type) {
327     case FocusIn:
328     case FocusOut:
329         client_set_focused(client, e->type == FocusIn);
330         break;
331     case ConfigureRequest:
332         /* compress these */
333         while (XCheckTypedWindowEvent(ob_display, client->window,
334                                       ConfigureRequest, &ce)) {
335             ++i;
336             /* XXX if this causes bad things.. we can compress config req's
337                with the same mask. */
338             e->xconfigurerequest.value_mask |=
339                 ce.xconfigurerequest.value_mask;
340             if (ce.xconfigurerequest.value_mask & CWX)
341                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
342             if (ce.xconfigurerequest.value_mask & CWY)
343                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
344             if (ce.xconfigurerequest.value_mask & CWWidth)
345                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
346             if (ce.xconfigurerequest.value_mask & CWHeight)
347                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
348             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
349                 e->xconfigurerequest.border_width =
350                     ce.xconfigurerequest.border_width;
351             if (ce.xconfigurerequest.value_mask & CWStackMode)
352                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
353         }
354         if (i) g_message("Compressed %d Configures", i);
355
356         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
357         if (client->iconic || client->shaded) return;
358
359         if (e->xconfigurerequest.value_mask & CWBorderWidth)
360             client->border_width = e->xconfigurerequest.border_width;
361
362         /* resize, then move, as specified in the EWMH section 7.7 */
363         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
364                                                CWX | CWY)) {
365             int x, y, w, h;
366             Corner corner;
367                
368             x = (e->xconfigurerequest.value_mask & CWX) ?
369                 e->xconfigurerequest.x : client->area.x;
370             y = (e->xconfigurerequest.value_mask & CWY) ?
371                 e->xconfigurerequest.y : client->area.y;
372             w = (e->xconfigurerequest.value_mask & CWWidth) ?
373                 e->xconfigurerequest.width : client->area.width;
374             h = (e->xconfigurerequest.value_mask & CWHeight) ?
375                 e->xconfigurerequest.height : client->area.height;
376                
377             switch (client->gravity) {
378             case NorthEastGravity:
379             case EastGravity:
380                 corner = Corner_TopRight;
381                 break;
382             case SouthWestGravity:
383             case SouthGravity:
384                 corner = Corner_BottomLeft;
385                 break;
386             case SouthEastGravity:
387                 corner = Corner_BottomRight;
388                 break;
389             default:     /* NorthWest, Static, etc */
390                 corner = Corner_TopLeft;
391             }
392
393             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
394         }
395
396         if (e->xconfigurerequest.value_mask & CWStackMode) {
397             switch (e->xconfigurerequest.detail) {
398             case Below:
399             case BottomIf:
400                 stacking_lower(client);
401                 break;
402
403             case Above:
404             case TopIf:
405             default:
406                 stacking_raise(client);
407                 break;
408             }
409         }
410         break;
411     case UnmapNotify:
412         if (client->ignore_unmaps) {
413             client->ignore_unmaps--;
414             break;
415         }
416         client_unmanage(client);
417         break;
418     case DestroyNotify:
419         client_unmanage(client);
420         break;
421     case ReparentNotify:
422         /* this is when the client is first taken captive in the frame */
423         if (e->xreparent.parent == client->frame->plate) break;
424
425         /*
426           This event is quite rare and is usually handled in unmapHandler.
427           However, if the window is unmapped when the reparent event occurs,
428           the window manager never sees it because an unmap event is not sent
429           to an already unmapped window.
430         */
431
432         /* we don't want the reparent event, put it back on the stack for the
433            X server to deal with after we unmanage the window */
434         XPutBackEvent(ob_display, e);
435      
436         client_unmanage(client);
437         break;
438     case MapRequest:
439         if (!client->iconic) break; /* this normally doesn't happen, but if it
440                                        does, we don't want it! */
441         if (screen_showing_desktop)
442             screen_show_desktop(FALSE);
443         client_iconify(client, FALSE, TRUE);
444         if (!client->frame->visible)
445             /* if its not visible still, then don't mess with it */
446             break;
447         if (client->shaded)
448             client_shade(client, FALSE);
449         client_focus(client);
450         stacking_raise(client);
451         break;
452     case ClientMessage:
453         /* validate cuz we query stuff off the client here */
454         if (!client_validate(client)) break;
455   
456         if (e->xclient.format != 32) return;
457
458         msgtype = e->xclient.message_type;
459         if (msgtype == prop_atoms.wm_change_state) {
460             /* compress changes into a single change */
461             while (XCheckTypedWindowEvent(ob_display, e->type,
462                                           client->window, &ce)) {
463                 /* XXX: it would be nice to compress ALL messages of a
464                    type, not just messages in a row without other
465                    message types between. */
466                 if (ce.xclient.message_type != msgtype) {
467                     XPutBackEvent(ob_display, &ce);
468                     break;
469                 }
470                 e->xclient = ce.xclient;
471             }
472             client_set_wm_state(client, e->xclient.data.l[0]);
473         } else if (msgtype == prop_atoms.net_wm_desktop) {
474             /* compress changes into a single change */
475             while (XCheckTypedWindowEvent(ob_display, e->type,
476                                           client->window, &ce)) {
477                 /* XXX: it would be nice to compress ALL messages of a
478                    type, not just messages in a row without other
479                    message types between. */
480                 if (ce.xclient.message_type != msgtype) {
481                     XPutBackEvent(ob_display, &ce);
482                     break;
483                 }
484                 e->xclient = ce.xclient;
485             }
486             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
487                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
488                 client_set_desktop(client, (unsigned)e->xclient.data.l[0]);
489         } else if (msgtype == prop_atoms.net_wm_state) {
490             /* can't compress these */
491             g_message("net_wm_state %s %ld %ld for 0x%lx",
492                       (e->xclient.data.l[0] == 0 ? "Remove" :
493                        e->xclient.data.l[0] == 1 ? "Add" :
494                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
495                       e->xclient.data.l[1], e->xclient.data.l[2],
496                       client->window);
497             client_set_state(client, e->xclient.data.l[0],
498                              e->xclient.data.l[1], e->xclient.data.l[2]);
499         } else if (msgtype == prop_atoms.net_close_window) {
500             g_message("net_close_window for 0x%lx", client->window);
501             client_close(client);
502         } else if (msgtype == prop_atoms.net_active_window) {
503             g_message("net_active_window for 0x%lx", client->window);
504             if (screen_showing_desktop)
505                 screen_show_desktop(FALSE);
506             if (client->iconic)
507                 client_iconify(client, FALSE, TRUE);
508             else if (!client->frame->visible)
509                 /* if its not visible for other reasons, then don't mess
510                    with it */
511                 break;
512             if (client->shaded)
513                 client_shade(client, FALSE);
514             client_focus(client);
515             stacking_raise(client);
516         }
517         break;
518     case PropertyNotify:
519         /* validate cuz we query stuff off the client here */
520         if (!client_validate(client)) break;
521   
522         /* compress changes to a single property into a single change */
523         while (XCheckTypedWindowEvent(ob_display, e->type,
524                                       client->window, &ce)) {
525             /* XXX: it would be nice to compress ALL changes to a property,
526                not just changes in a row without other props between. */
527             if (ce.xproperty.atom != e->xproperty.atom) {
528                 XPutBackEvent(ob_display, &ce);
529                 break;
530             }
531         }
532
533         msgtype = e->xproperty.atom;
534         if (msgtype == XA_WM_NORMAL_HINTS) {
535             client_update_normal_hints(client);
536             /* normal hints can make a window non-resizable */
537             client_setup_decor_and_functions(client);
538         }
539         else if (msgtype == XA_WM_HINTS)
540             client_update_wmhints(client);
541         else if (msgtype == XA_WM_TRANSIENT_FOR) {
542             client_update_transient_for(client);
543             client_get_type(client);
544             /* type may have changed, so update the layer */
545             client_calc_layer(client);
546             client_setup_decor_and_functions(client);
547         }
548         else if (msgtype == prop_atoms.net_wm_name ||
549                  msgtype == prop_atoms.wm_name)
550             client_update_title(client);
551         else if (msgtype == prop_atoms.net_wm_icon_name ||
552                  msgtype == prop_atoms.wm_icon_name)
553             client_update_icon_title(client);
554         else if (msgtype == prop_atoms.wm_class)
555             client_update_class(client);
556         else if (msgtype == prop_atoms.wm_protocols) {
557             client_update_protocols(client);
558             client_setup_decor_and_functions(client);
559         }
560         else if (msgtype == prop_atoms.net_wm_strut)
561             client_update_strut(client);
562         else if (msgtype == prop_atoms.net_wm_icon)
563             client_update_icons(client);
564         else if (msgtype == prop_atoms.kwm_win_icon)
565             client_update_kwm_icon(client);
566     default:
567         ;
568 #ifdef SHAPE
569         if (extensions_shape && e->type == extensions_shape_event_basep) {
570             client->shaped = ((XShapeEvent*)e)->shaped;
571             engine_frame_adjust_shape(client->frame);
572         }
573 #endif
574     }
575 }