more focus improvements. everything seems to work!
[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         if (e->xfocus.mode == NotifyGrab ||
223             !(e->xfocus.detail == NotifyNonlinearVirtual ||
224               e->xfocus.detail == NotifyNonlinear))
225             return;
226         g_message("FOCUS IN %lx", window);
227         break;
228     case FocusOut:
229         if (e->xfocus.mode == NotifyGrab ||
230             !(e->xfocus.detail == NotifyNonlinearVirtual ||
231               e->xfocus.detail == NotifyNonlinear))
232             return;
233
234         /* FocusOut events just make us look for FocusIn events. They
235            are mostly ignored otherwise. */
236         {
237             XEvent fi;
238             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
239                 g_message("FOCUS OUT %lx IN %lx", e->xfocus.window, fi.xfocus.window);
240                 event_process(&fi);
241
242                 if (fi.xfocus.window == e->xfocus.window)
243                     return;
244             } else {
245                 g_message("FOCUS OUT %lx IN 0x0", e->xfocus.window);
246             }
247         }
248         break;
249     case EnterNotify:
250     case LeaveNotify:
251         event_lasttime = e->xcrossing.time;
252         /* XXX this caused problems before... but i don't remember why. hah.
253            so back it is. if problems arise again, then try filtering on the
254            detail instead of the mode. */
255         if (e->xcrossing.mode != NotifyNormal) return;
256         break;
257     }
258
259     client = g_hash_table_lookup(client_map, (gpointer)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         g_message("Proxying configure event for 0x%lx", window);
282        
283         /* we are not to be held responsible if someone sends us an
284            invalid request! */
285         xerror_set_ignore(TRUE);
286         XConfigureWindow(ob_display, window,
287                          e->xconfigurerequest.value_mask, &xwc);
288         xerror_set_ignore(FALSE);
289     }
290
291     /* dispatch the event to registered handlers */
292     dispatch_x(e, client);
293 }
294
295 static void event_handle_root(XEvent *e)
296 {
297     Atom msgtype;
298      
299     switch(e->type) {
300     case ClientMessage:
301         if (e->xclient.format != 32) break;
302
303         msgtype = e->xclient.message_type;
304         if (msgtype == prop_atoms.net_current_desktop) {
305             unsigned int d = e->xclient.data.l[0];
306             if (d <= screen_num_desktops)
307                 screen_set_desktop(d);
308         } else if (msgtype == prop_atoms.net_number_of_desktops) {
309             unsigned int d = e->xclient.data.l[0];
310             if (d > 0)
311                 screen_set_num_desktops(d);
312         } else if (msgtype == prop_atoms.net_showing_desktop) {
313             screen_show_desktop(e->xclient.data.l[0] != 0);
314         }
315         break;
316     case PropertyNotify:
317         if (e->xproperty.atom == prop_atoms.net_desktop_names)
318             screen_update_desktop_names();
319         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
320             screen_update_layout();
321         break;
322     }
323 }
324
325 static void event_handle_client(Client *client, XEvent *e)
326 {
327     XEvent ce;
328     Atom msgtype;
329      
330     switch (e->type) {
331     case FocusIn:
332     case FocusOut:
333         client_set_focused(client, e->type == FocusIn);
334         break;
335     case ConfigureRequest:
336         g_message("ConfigureRequest for window %lx", client->window);
337         /* compress these */
338         while (XCheckTypedWindowEvent(ob_display, client->window,
339                                       ConfigureRequest, &ce)) {
340             /* XXX if this causes bad things.. we can compress config req's
341                with the same mask. */
342             e->xconfigurerequest.value_mask |=
343                 ce.xconfigurerequest.value_mask;
344             if (ce.xconfigurerequest.value_mask & CWX)
345                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
346             if (ce.xconfigurerequest.value_mask & CWY)
347                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
348             if (ce.xconfigurerequest.value_mask & CWWidth)
349                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
350             if (ce.xconfigurerequest.value_mask & CWHeight)
351                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
352             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
353                 e->xconfigurerequest.border_width =
354                     ce.xconfigurerequest.border_width;
355             if (ce.xconfigurerequest.value_mask & CWStackMode)
356                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
357         }
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         g_message("UnmapNotify for %lx", client->window);
420         client_unmanage(client);
421         break;
422     case DestroyNotify:
423         g_message("DestroyNotify for %lx", client->window);
424         client_unmanage(client);
425         break;
426     case ReparentNotify:
427         /* this is when the client is first taken captive in the frame */
428         if (e->xreparent.parent == client->frame->plate) break;
429
430         /*
431           This event is quite rare and is usually handled in unmapHandler.
432           However, if the window is unmapped when the reparent event occurs,
433           the window manager never sees it because an unmap event is not sent
434           to an already unmapped window.
435         */
436
437         /* we don't want the reparent event, put it back on the stack for the
438            X server to deal with after we unmanage the window */
439         XPutBackEvent(ob_display, e);
440      
441         client_unmanage(client);
442         break;
443     case MapRequest:
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             client_set_desktop(client, e->xclient.data.l[0]);
490         } else if (msgtype == prop_atoms.net_wm_state) {
491             /* can't compress these */
492             g_message("net_wm_state %s %ld %ld for 0x%lx",
493                       (e->xclient.data.l[0] == 0 ? "Remove" :
494                        e->xclient.data.l[0] == 1 ? "Add" :
495                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
496                       e->xclient.data.l[1], e->xclient.data.l[2],
497                       client->window);
498             client_set_state(client, e->xclient.data.l[0],
499                              e->xclient.data.l[1], e->xclient.data.l[2]);
500         } else if (msgtype == prop_atoms.net_close_window) {
501             g_message("net_close_window for 0x%lx", client->window);
502             client_close(client);
503         } else if (msgtype == prop_atoms.net_active_window) {
504             g_message("net_active_window for 0x%lx", client->window);
505             if (screen_showing_desktop)
506                 screen_show_desktop(FALSE);
507             if (client->iconic)
508                 client_iconify(client, FALSE, TRUE);
509             else if (!client->frame->visible)
510                 /* if its not visible for other reasons, then don't mess
511                    with it */
512                 break;
513             if (client->shaded)
514                 client_shade(client, FALSE);
515             client_focus(client);
516             stacking_raise(client);
517         }
518         break;
519     case PropertyNotify:
520         /* validate cuz we query stuff off the client here */
521         if (!client_validate(client)) break;
522   
523         /* compress changes to a single property into a single change */
524         while (XCheckTypedWindowEvent(ob_display, e->type,
525                                       client->window, &ce)) {
526             /* XXX: it would be nice to compress ALL changes to a property,
527                not just changes in a row without other props between. */
528             if (ce.xproperty.atom != e->xproperty.atom) {
529                 XPutBackEvent(ob_display, &ce);
530                 break;
531             }
532         }
533
534         msgtype = e->xproperty.atom;
535         if (msgtype == XA_WM_NORMAL_HINTS) {
536             client_update_normal_hints(client);
537             /* normal hints can make a window non-resizable */
538             client_setup_decor_and_functions(client);
539         }
540         else if (msgtype == XA_WM_HINTS)
541             client_update_wmhints(client);
542         else if (msgtype == XA_WM_TRANSIENT_FOR) {
543             client_update_transient_for(client);
544             client_get_type(client);
545             /* type may have changed, so update the layer */
546             client_calc_layer(client);
547             client_setup_decor_and_functions(client);
548         }
549         else if (msgtype == prop_atoms.net_wm_name ||
550                  msgtype == prop_atoms.wm_name)
551             client_update_title(client);
552         else if (msgtype == prop_atoms.net_wm_icon_name ||
553                  msgtype == prop_atoms.wm_icon_name)
554             client_update_icon_title(client);
555         else if (msgtype == prop_atoms.wm_class)
556             client_update_class(client);
557         else if (msgtype == prop_atoms.wm_protocols) {
558             client_update_protocols(client);
559             client_setup_decor_and_functions(client);
560         }
561         else if (msgtype == prop_atoms.net_wm_strut)
562             client_update_strut(client);
563         else if (msgtype == prop_atoms.net_wm_icon)
564             client_update_icons(client);
565         else if (msgtype == prop_atoms.kwm_win_icon)
566             client_update_kwm_icon(client);
567     }
568 }