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