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