set the last time to CurrentTime for events that don't have time stamps
[dana/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     default:
255         event_lasttime = CurrentTime;
256         break;
257     }
258
259     client = g_hash_table_lookup(client_map, &window);
260
261     /* deal with it in the kernel */
262     if (client)
263         event_handle_client(client, e);
264     else if (window == ob_root)
265         event_handle_root(e);
266     else if (e->type == MapRequest)
267         client_manage(window);
268     else if (e->type == ConfigureRequest) {
269         /* unhandled configure requests must be used to configure the
270            window directly */
271         XWindowChanges xwc;
272                
273         xwc.x = e->xconfigurerequest.x;
274         xwc.y = e->xconfigurerequest.y;
275         xwc.width = e->xconfigurerequest.width;
276         xwc.height = e->xconfigurerequest.height;
277         xwc.border_width = e->xconfigurerequest.border_width;
278         xwc.sibling = e->xconfigurerequest.above;
279         xwc.stack_mode = e->xconfigurerequest.detail;
280        
281         /* we are not to be held responsible if someone sends us an
282            invalid request! */
283         xerror_set_ignore(TRUE);
284         XConfigureWindow(ob_display, window,
285                          e->xconfigurerequest.value_mask, &xwc);
286         xerror_set_ignore(FALSE);
287     }
288
289     /* dispatch the event to registered handlers */
290     dispatch_x(e, client);
291 }
292
293 static void event_handle_root(XEvent *e)
294 {
295     Atom msgtype;
296      
297     switch(e->type) {
298     case ClientMessage:
299         if (e->xclient.format != 32) break;
300
301         msgtype = e->xclient.message_type;
302         if (msgtype == prop_atoms.net_current_desktop) {
303             unsigned int d = e->xclient.data.l[0];
304             if (d < screen_num_desktops)
305                 screen_set_desktop(d);
306         } else if (msgtype == prop_atoms.net_number_of_desktops) {
307             unsigned int d = e->xclient.data.l[0];
308             if (d > 0)
309                 screen_set_num_desktops(d);
310         } else if (msgtype == prop_atoms.net_showing_desktop) {
311             screen_show_desktop(e->xclient.data.l[0] != 0);
312         }
313         break;
314     case PropertyNotify:
315         if (e->xproperty.atom == prop_atoms.net_desktop_names)
316             screen_update_desktop_names();
317         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
318             screen_update_layout();
319         break;
320     }
321 }
322
323 static void event_handle_client(Client *client, XEvent *e)
324 {
325     XEvent ce;
326     Atom msgtype;
327         int i=0;
328      
329     switch (e->type) {
330     case FocusIn:
331     case FocusOut:
332         client_set_focused(client, e->type == FocusIn);
333         break;
334     case ConfigureRequest:
335         /* compress these */
336         while (XCheckTypedWindowEvent(ob_display, client->window,
337                                       ConfigureRequest, &ce)) {
338             ++i;
339             /* XXX if this causes bad things.. we can compress config req's
340                with the same mask. */
341             e->xconfigurerequest.value_mask |=
342                 ce.xconfigurerequest.value_mask;
343             if (ce.xconfigurerequest.value_mask & CWX)
344                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
345             if (ce.xconfigurerequest.value_mask & CWY)
346                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
347             if (ce.xconfigurerequest.value_mask & CWWidth)
348                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
349             if (ce.xconfigurerequest.value_mask & CWHeight)
350                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
351             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
352                 e->xconfigurerequest.border_width =
353                     ce.xconfigurerequest.border_width;
354             if (ce.xconfigurerequest.value_mask & CWStackMode)
355                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
356         }
357         if (i) g_message("Compressed %d Configures", i);
358
359         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
360         if (client->iconic || client->shaded) return;
361
362         if (e->xconfigurerequest.value_mask & CWBorderWidth)
363             client->border_width = e->xconfigurerequest.border_width;
364
365         /* resize, then move, as specified in the EWMH section 7.7 */
366         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
367                                                CWX | CWY)) {
368             int x, y, w, h;
369             Corner corner;
370                
371             x = (e->xconfigurerequest.value_mask & CWX) ?
372                 e->xconfigurerequest.x : client->area.x;
373             y = (e->xconfigurerequest.value_mask & CWY) ?
374                 e->xconfigurerequest.y : client->area.y;
375             w = (e->xconfigurerequest.value_mask & CWWidth) ?
376                 e->xconfigurerequest.width : client->area.width;
377             h = (e->xconfigurerequest.value_mask & CWHeight) ?
378                 e->xconfigurerequest.height : client->area.height;
379                
380             switch (client->gravity) {
381             case NorthEastGravity:
382             case EastGravity:
383                 corner = Corner_TopRight;
384                 break;
385             case SouthWestGravity:
386             case SouthGravity:
387                 corner = Corner_BottomLeft;
388                 break;
389             case SouthEastGravity:
390                 corner = Corner_BottomRight;
391                 break;
392             default:     /* NorthWest, Static, etc */
393                 corner = Corner_TopLeft;
394             }
395
396             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
397         }
398
399         if (e->xconfigurerequest.value_mask & CWStackMode) {
400             switch (e->xconfigurerequest.detail) {
401             case Below:
402             case BottomIf:
403                 stacking_lower(client);
404                 break;
405
406             case Above:
407             case TopIf:
408             default:
409                 stacking_raise(client);
410                 break;
411             }
412         }
413         break;
414     case UnmapNotify:
415         if (client->ignore_unmaps) {
416             client->ignore_unmaps--;
417             break;
418         }
419         client_unmanage(client);
420         break;
421     case DestroyNotify:
422         client_unmanage(client);
423         break;
424     case ReparentNotify:
425         /* this is when the client is first taken captive in the frame */
426         if (e->xreparent.parent == client->frame->plate) break;
427
428         /*
429           This event is quite rare and is usually handled in unmapHandler.
430           However, if the window is unmapped when the reparent event occurs,
431           the window manager never sees it because an unmap event is not sent
432           to an already unmapped window.
433         */
434
435         /* we don't want the reparent event, put it back on the stack for the
436            X server to deal with after we unmanage the window */
437         XPutBackEvent(ob_display, e);
438      
439         client_unmanage(client);
440         break;
441     case MapRequest:
442         if (!client->iconic) break; /* this normally doesn't happen, but if it
443                                        does, we don't want it! */
444         if (screen_showing_desktop)
445             screen_show_desktop(FALSE);
446         client_iconify(client, FALSE, TRUE);
447         if (!client->frame->visible)
448             /* if its not visible still, then don't mess with it */
449             break;
450         if (client->shaded)
451             client_shade(client, FALSE);
452         client_focus(client);
453         stacking_raise(client);
454         break;
455     case ClientMessage:
456         /* validate cuz we query stuff off the client here */
457         if (!client_validate(client)) break;
458   
459         if (e->xclient.format != 32) return;
460
461         msgtype = e->xclient.message_type;
462         if (msgtype == prop_atoms.wm_change_state) {
463             /* compress changes into a single change */
464             while (XCheckTypedWindowEvent(ob_display, e->type,
465                                           client->window, &ce)) {
466                 /* XXX: it would be nice to compress ALL messages of a
467                    type, not just messages in a row without other
468                    message types between. */
469                 if (ce.xclient.message_type != msgtype) {
470                     XPutBackEvent(ob_display, &ce);
471                     break;
472                 }
473                 e->xclient = ce.xclient;
474             }
475             client_set_wm_state(client, e->xclient.data.l[0]);
476         } else if (msgtype == prop_atoms.net_wm_desktop) {
477             /* compress changes into a single change */
478             while (XCheckTypedWindowEvent(ob_display, e->type,
479                                           client->window, &ce)) {
480                 /* XXX: it would be nice to compress ALL messages of a
481                    type, not just messages in a row without other
482                    message types between. */
483                 if (ce.xclient.message_type != msgtype) {
484                     XPutBackEvent(ob_display, &ce);
485                     break;
486                 }
487                 e->xclient = ce.xclient;
488             }
489             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
490                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
491                 client_set_desktop(client, (unsigned)e->xclient.data.l[0]);
492         } else if (msgtype == prop_atoms.net_wm_state) {
493             /* can't compress these */
494             g_message("net_wm_state %s %ld %ld for 0x%lx",
495                       (e->xclient.data.l[0] == 0 ? "Remove" :
496                        e->xclient.data.l[0] == 1 ? "Add" :
497                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
498                       e->xclient.data.l[1], e->xclient.data.l[2],
499                       client->window);
500             client_set_state(client, e->xclient.data.l[0],
501                              e->xclient.data.l[1], e->xclient.data.l[2]);
502         } else if (msgtype == prop_atoms.net_close_window) {
503             g_message("net_close_window for 0x%lx", client->window);
504             client_close(client);
505         } else if (msgtype == prop_atoms.net_active_window) {
506             g_message("net_active_window for 0x%lx", client->window);
507             if (screen_showing_desktop)
508                 screen_show_desktop(FALSE);
509             if (client->iconic)
510                 client_iconify(client, FALSE, TRUE);
511             else if (!client->frame->visible)
512                 /* if its not visible for other reasons, then don't mess
513                    with it */
514                 break;
515             if (client->shaded)
516                 client_shade(client, FALSE);
517             client_focus(client);
518             stacking_raise(client);
519         }
520         break;
521     case PropertyNotify:
522         /* validate cuz we query stuff off the client here */
523         if (!client_validate(client)) break;
524   
525         /* compress changes to a single property into a single change */
526         while (XCheckTypedWindowEvent(ob_display, e->type,
527                                       client->window, &ce)) {
528             /* XXX: it would be nice to compress ALL changes to a property,
529                not just changes in a row without other props between. */
530             if (ce.xproperty.atom != e->xproperty.atom) {
531                 XPutBackEvent(ob_display, &ce);
532                 break;
533             }
534         }
535
536         msgtype = e->xproperty.atom;
537         if (msgtype == XA_WM_NORMAL_HINTS) {
538             client_update_normal_hints(client);
539             /* normal hints can make a window non-resizable */
540             client_setup_decor_and_functions(client);
541         }
542         else if (msgtype == XA_WM_HINTS)
543             client_update_wmhints(client);
544         else if (msgtype == XA_WM_TRANSIENT_FOR) {
545             client_update_transient_for(client);
546             client_get_type(client);
547             /* type may have changed, so update the layer */
548             client_calc_layer(client);
549             client_setup_decor_and_functions(client);
550         }
551         else if (msgtype == prop_atoms.net_wm_name ||
552                  msgtype == prop_atoms.wm_name)
553             client_update_title(client);
554         else if (msgtype == prop_atoms.net_wm_icon_name ||
555                  msgtype == prop_atoms.wm_icon_name)
556             client_update_icon_title(client);
557         else if (msgtype == prop_atoms.wm_class)
558             client_update_class(client);
559         else if (msgtype == prop_atoms.wm_protocols) {
560             client_update_protocols(client);
561             client_setup_decor_and_functions(client);
562         }
563         else if (msgtype == prop_atoms.net_wm_strut)
564             client_update_strut(client);
565         else if (msgtype == prop_atoms.net_wm_icon)
566             client_update_icons(client);
567         else if (msgtype == prop_atoms.kwm_win_icon)
568             client_update_kwm_icon(client);
569     default:
570         ;
571 #ifdef SHAPE
572         if (extensions_shape && e->type == extensions_shape_event_basep) {
573             client->shaped = ((XShapeEvent*)e)->shaped;
574             engine_frame_adjust_shape(client->frame);
575         }
576 #endif
577     }
578 }