yet more improvements to focus handling
[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
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 void event_process(XEvent *e)
116 {
117     XEvent ce;
118     KeyCode *kp;
119     Window window;
120     int i, k;
121     Client *client;
122
123     /* pick a window */
124     switch (e->type) {
125     case MapRequest:
126         window = e->xmap.window;
127         break;
128     case UnmapNotify:
129         window = e->xunmap.window;
130         break;
131     case DestroyNotify:
132         window = e->xdestroywindow.window;
133         break;
134     case ConfigureRequest:
135         window = e->xconfigurerequest.window;
136         break;
137     default:
138         /* XKB events */
139         if (e->type == extensions_xkb_event_basep) {
140             switch (((XkbAnyEvent*)&e)->xkb_type) {
141             case XkbBellNotify:
142                 window = ((XkbBellNotifyEvent*)&e)->window;
143             default:
144                 window = None;
145             }
146         } else
147             window = e->xany.window;
148     }
149      
150     /* grab the lasttime and hack up the state */
151     switch (e->type) {
152     case ButtonPress:
153     case ButtonRelease:
154         event_lasttime = e->xbutton.time;
155         e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask);
156         /* kill off the Button1Mask etc, only want the modifiers */
157         e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask |
158                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
159         break;
160     case KeyPress:
161         event_lasttime = e->xkey.time;
162         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
163         /* kill off the Button1Mask etc, only want the modifiers */
164         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
165                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
166         /* add to the state the mask of the modifier being pressed, if it is
167            a modifier key being pressed (this is a little ugly..) */
168 /* I'm commenting this out cuz i don't want "C-Control_L" being returned. */
169 /*      kp = modmap->modifiermap;*/
170 /*      for (i = 0; i < mask_table_size; ++i) {*/
171 /*          for (k = 0; k < modmap->max_keypermod; ++k) {*/
172 /*              if (*kp == e->xkey.keycode) {*/ /* found the keycode */
173                     /* add the mask for it */
174 /*                  e->xkey.state |= mask_table[i];*/
175                     /* cause the first loop to break; */
176 /*                  i = mask_table_size;*/
177 /*                  break;*/ /* get outta here! */
178 /*              }*/
179 /*              ++kp;*/
180 /*          }*/
181 /*      }*/
182
183         break;
184     case KeyRelease:
185         event_lasttime = e->xkey.time;
186         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
187         /* kill off the Button1Mask etc, only want the modifiers */
188         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
189                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
190         /* remove from the state the mask of the modifier being released, if
191            it is a modifier key being released (this is a little ugly..) */
192         kp = modmap->modifiermap;
193         for (i = 0; i < mask_table_size; ++i) {
194             for (k = 0; k < modmap->max_keypermod; ++k) {
195                 if (*kp == e->xkey.keycode) { /* found the keycode */
196                     /* remove the mask for it */
197                     e->xkey.state &= ~mask_table[i];
198                     /* cause the first loop to break; */
199                     i = mask_table_size;
200                     break; /* get outta here! */
201                 }
202                 ++kp;
203             }
204         }
205         break;
206     case MotionNotify:
207         event_lasttime = e->xmotion.time;
208         e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask);
209         /* kill off the Button1Mask etc, only want the modifiers */
210         e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask |
211                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
212         /* compress events */
213         while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) {
214             e->xmotion.x_root = ce.xmotion.x_root;
215             e->xmotion.y_root = ce.xmotion.y_root;
216         }
217         break;
218     case PropertyNotify:
219         event_lasttime = e->xproperty.time;
220         break;
221     case FocusIn:
222     case FocusOut:
223         if (e->xfocus.mode == NotifyGrab)
224             /*|| e.xfocus.mode == NotifyUngrab ||*/
225                
226             /* From Metacity, from WindowMaker, ignore all funky pointer
227                root events. Its commented out cuz I don't think we need this
228                at all. If problems arise we can look into it */
229             /*e.xfocus.detail > NotifyNonlinearVirtual) */
230             return; /* skip me! */
231         if (e->type == FocusOut) {
232             /* FocusOut events just make us look for FocusIn events. They
233                are mostly ignored otherwise. */
234             XEvent fi;
235             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
236                 event_process(&fi);
237                 /* dont unfocus the window we just focused! */
238                 if (fi.xfocus.window == e->xfocus.window)
239                     return;
240             }
241         } else if (window == focus_backup && focus_client != NULL)
242             /* Something's focused but we got a focus event for the backup
243                window. this means that something unfocused before we received
244                the new FocusIn. Just ignore it. */
245                return;
246         break;
247     case EnterNotify:
248     case LeaveNotify:
249         event_lasttime = e->xcrossing.time;
250         /* XXX this caused problems before... but i don't remember why. hah.
251            so back it is. if problems arise again, then try filtering on the
252            detail instead of the mode. */
253         if (e->xcrossing.mode != NotifyNormal) return;
254         break;
255     }
256
257     client = g_hash_table_lookup(client_map, (gpointer)window);
258
259     /* deal with it in the kernel */
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 == MapRequest)
265         client_manage(window);
266     else if (e->type == ConfigureRequest) {
267         /* unhandled configure requests must be used to configure the
268            window directly */
269         XWindowChanges xwc;
270                
271         xwc.x = e->xconfigurerequest.x;
272         xwc.y = e->xconfigurerequest.y;
273         xwc.width = e->xconfigurerequest.width;
274         xwc.height = e->xconfigurerequest.height;
275         xwc.border_width = e->xconfigurerequest.border_width;
276         xwc.sibling = e->xconfigurerequest.above;
277         xwc.stack_mode = e->xconfigurerequest.detail;
278        
279         g_message("Proxying configure event for 0x%lx", window);
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      
328     switch (e->type) {
329     case FocusIn:
330         if (focus_client != client)
331             focus_set_client(client);
332
333         /* focus state can affect the stacking layer */
334         client_calc_layer(client);
335
336         engine_frame_adjust_focus(client->frame);
337         break;
338     case FocusOut:
339         if (focus_client == client)
340             focus_set_client(NULL);
341
342         /* focus state can affect the stacking layer */
343         client_calc_layer(client);
344
345         engine_frame_adjust_focus(client->frame);
346         break;
347     case ConfigureRequest:
348         g_message("ConfigureRequest for window %lx", client->window);
349         /* compress these */
350         while (XCheckTypedWindowEvent(ob_display, client->window,
351                                       ConfigureRequest, &ce)) {
352             /* XXX if this causes bad things.. we can compress config req's
353                with the same mask. */
354             e->xconfigurerequest.value_mask |=
355                 ce.xconfigurerequest.value_mask;
356             if (ce.xconfigurerequest.value_mask & CWX)
357                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
358             if (ce.xconfigurerequest.value_mask & CWY)
359                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
360             if (ce.xconfigurerequest.value_mask & CWWidth)
361                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
362             if (ce.xconfigurerequest.value_mask & CWHeight)
363                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
364             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
365                 e->xconfigurerequest.border_width =
366                     ce.xconfigurerequest.border_width;
367             if (ce.xconfigurerequest.value_mask & CWStackMode)
368                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
369         }
370
371         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
372         if (client->iconic || client->shaded) return;
373
374         if (e->xconfigurerequest.value_mask & CWBorderWidth)
375             client->border_width = e->xconfigurerequest.border_width;
376
377         /* resize, then move, as specified in the EWMH section 7.7 */
378         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
379                                                CWX | CWY)) {
380             int x, y, w, h;
381             Corner corner;
382                
383             x = (e->xconfigurerequest.value_mask & CWX) ?
384                 e->xconfigurerequest.x : client->area.x;
385             y = (e->xconfigurerequest.value_mask & CWY) ?
386                 e->xconfigurerequest.y : client->area.y;
387             w = (e->xconfigurerequest.value_mask & CWWidth) ?
388                 e->xconfigurerequest.width : client->area.width;
389             h = (e->xconfigurerequest.value_mask & CWHeight) ?
390                 e->xconfigurerequest.height : client->area.height;
391                
392             switch (client->gravity) {
393             case NorthEastGravity:
394             case EastGravity:
395                 corner = Corner_TopRight;
396                 break;
397             case SouthWestGravity:
398             case SouthGravity:
399                 corner = Corner_BottomLeft;
400                 break;
401             case SouthEastGravity:
402                 corner = Corner_BottomRight;
403                 break;
404             default:     /* NorthWest, Static, etc */
405                 corner = Corner_TopLeft;
406             }
407
408             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
409         }
410
411         if (e->xconfigurerequest.value_mask & CWStackMode) {
412             switch (e->xconfigurerequest.detail) {
413             case Below:
414             case BottomIf:
415                 stacking_lower(client);
416                 break;
417
418             case Above:
419             case TopIf:
420             default:
421                 stacking_raise(client);
422                 break;
423             }
424         }
425         break;
426     case UnmapNotify:
427         if (client->ignore_unmaps) {
428             client->ignore_unmaps--;
429             break;
430         }
431         g_message("UnmapNotify for %lx", client->window);
432         client_unmanage(client);
433         break;
434     case DestroyNotify:
435         g_message("DestroyNotify for %lx", client->window);
436         client_unmanage(client);
437         break;
438     case ReparentNotify:
439         /* this is when the client is first taken captive in the frame */
440         if (e->xreparent.parent == client->frame->plate) break;
441
442         /*
443           This event is quite rare and is usually handled in unmapHandler.
444           However, if the window is unmapped when the reparent event occurs,
445           the window manager never sees it because an unmap event is not sent
446           to an already unmapped window.
447         */
448
449         /* we don't want the reparent event, put it back on the stack for the
450            X server to deal with after we unmanage the window */
451         XPutBackEvent(ob_display, e);
452      
453         client_unmanage(client);
454         break;
455     case MapRequest:
456         if (screen_showing_desktop)
457             screen_show_desktop(FALSE);
458         client_iconify(client, FALSE, TRUE);
459         if (!client->frame->visible)
460             /* if its not visible still, then don't mess with it */
461             break;
462         if (client->shaded)
463             client_shade(client, FALSE);
464         client_focus(client);
465         stacking_raise(client);
466         break;
467     case ClientMessage:
468         /* validate cuz we query stuff off the client here */
469         if (!client_validate(client)) break;
470   
471         if (e->xclient.format != 32) return;
472
473         msgtype = e->xclient.message_type;
474         if (msgtype == prop_atoms.wm_change_state) {
475             /* compress changes into a single change */
476             while (XCheckTypedWindowEvent(ob_display, e->type,
477                                           client->window, &ce)) {
478                 /* XXX: it would be nice to compress ALL messages of a
479                    type, not just messages in a row without other
480                    message types between. */
481                 if (ce.xclient.message_type != msgtype) {
482                     XPutBackEvent(ob_display, &ce);
483                     break;
484                 }
485                 e->xclient = ce.xclient;
486             }
487             client_set_wm_state(client, e->xclient.data.l[0]);
488         } else if (msgtype == prop_atoms.net_wm_desktop) {
489             /* compress changes into a single change */
490             while (XCheckTypedWindowEvent(ob_display, e->type,
491                                           client->window, &ce)) {
492                 /* XXX: it would be nice to compress ALL messages of a
493                    type, not just messages in a row without other
494                    message types between. */
495                 if (ce.xclient.message_type != msgtype) {
496                     XPutBackEvent(ob_display, &ce);
497                     break;
498                 }
499                 e->xclient = ce.xclient;
500             }
501             client_set_desktop(client, e->xclient.data.l[0]);
502         } else if (msgtype == prop_atoms.net_wm_state) {
503             /* can't compress these */
504             g_message("net_wm_state %s %ld %ld for 0x%lx\n",
505                       (e->xclient.data.l[0] == 0 ? "Remove" :
506                        e->xclient.data.l[0] == 1 ? "Add" :
507                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
508                       e->xclient.data.l[1], e->xclient.data.l[2],
509                       client->window);
510             client_set_state(client, e->xclient.data.l[0],
511                              e->xclient.data.l[1], e->xclient.data.l[2]);
512         } else if (msgtype == prop_atoms.net_close_window) {
513             g_message("net_close_window for 0x%lx\n", client->window);
514             client_close(client);
515         } else if (msgtype == prop_atoms.net_active_window) {
516             g_message("net_active_window for 0x%lx\n", client->window);
517             if (screen_showing_desktop)
518                 screen_show_desktop(FALSE);
519             if (client->iconic)
520                 client_iconify(client, FALSE, TRUE);
521             else if (!client->frame->visible)
522                 /* if its not visible for other reasons, then don't mess
523                    with it */
524                 break;
525             if (client->shaded)
526                 client_shade(client, FALSE);
527             client_focus(client);
528             stacking_raise(client);
529         }
530         break;
531     case PropertyNotify:
532         /* validate cuz we query stuff off the client here */
533         if (!client_validate(client)) break;
534   
535         /* compress changes to a single property into a single change */
536         while (XCheckTypedWindowEvent(ob_display, e->type,
537                                       client->window, &ce)) {
538             /* XXX: it would be nice to compress ALL changes to a property,
539                not just changes in a row without other props between. */
540             if (ce.xproperty.atom != e->xproperty.atom) {
541                 XPutBackEvent(ob_display, &ce);
542                 break;
543             }
544         }
545
546         msgtype = e->xproperty.atom;
547         if (msgtype == XA_WM_NORMAL_HINTS) {
548             client_update_normal_hints(client);
549             /* normal hints can make a window non-resizable */
550             client_setup_decor_and_functions(client);
551         }
552         else if (msgtype == XA_WM_HINTS)
553             client_update_wmhints(client);
554         else if (msgtype == XA_WM_TRANSIENT_FOR) {
555             client_update_transient_for(client);
556             client_get_type(client);
557             /* type may have changed, so update the layer */
558             client_calc_layer(client);
559             client_setup_decor_and_functions(client);
560         }
561         else if (msgtype == prop_atoms.net_wm_name ||
562                  msgtype == prop_atoms.wm_name)
563             client_update_title(client);
564         else if (msgtype == prop_atoms.net_wm_icon_name ||
565                  msgtype == prop_atoms.wm_icon_name)
566             client_update_icon_title(client);
567         else if (msgtype == prop_atoms.wm_class)
568             client_update_class(client);
569         else if (msgtype == prop_atoms.wm_protocols) {
570             client_update_protocols(client);
571             client_setup_decor_and_functions(client);
572         }
573         else if (msgtype == prop_atoms.net_wm_strut)
574             client_update_strut(client);
575         else if (msgtype == prop_atoms.net_wm_icon)
576             client_update_icons(client);
577         else if (msgtype == prop_atoms.kwm_win_icon)
578             client_update_kwm_icon(client);
579     }
580 }