rm debug print
[dana/openbox.git] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "xerror.h"
4 #include "prop.h"
5 #include "screen.h"
6 #include "frame.h"
7 #include "engine.h"
8 #include "focus.h"
9 #include "stacking.h"
10 #include "extensions.h"
11 #include "timer.h"
12 #include "engine.h"
13 #include "dispatch.h"
14
15 #include <X11/Xlib.h>
16 #include <X11/keysym.h>
17 #include <X11/Xatom.h>
18
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         if (focus_client != client)
323             focus_set_client(client);
324
325         /* focus state can affect the stacking layer */
326         client_calc_layer(client);
327
328         engine_frame_adjust_focus(client->frame);
329         break;
330     case FocusOut:
331         if (focus_client == client)
332             focus_set_client(NULL);
333
334         /* focus state can affect the stacking layer */
335         client_calc_layer(client);
336
337         engine_frame_adjust_focus(client->frame);
338         break;
339     case ConfigureRequest:
340         g_message("ConfigureRequest for window %lx", client->window);
341         /* compress these */
342         while (XCheckTypedWindowEvent(ob_display, client->window,
343                                       ConfigureRequest, &ce)) {
344             /* XXX if this causes bad things.. we can compress config req's
345                with the same mask. */
346             e->xconfigurerequest.value_mask |=
347                 ce.xconfigurerequest.value_mask;
348             if (ce.xconfigurerequest.value_mask & CWX)
349                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
350             if (ce.xconfigurerequest.value_mask & CWY)
351                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
352             if (ce.xconfigurerequest.value_mask & CWWidth)
353                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
354             if (ce.xconfigurerequest.value_mask & CWHeight)
355                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
356             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
357                 e->xconfigurerequest.border_width =
358                     ce.xconfigurerequest.border_width;
359             if (ce.xconfigurerequest.value_mask & CWStackMode)
360                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
361         }
362
363         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
364         if (client->iconic || client->shaded) return;
365
366         if (e->xconfigurerequest.value_mask & CWBorderWidth)
367             client->border_width = e->xconfigurerequest.border_width;
368
369         /* resize, then move, as specified in the EWMH section 7.7 */
370         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
371                                                CWX | CWY)) {
372             int x, y, w, h;
373             Corner corner;
374                
375             x = (e->xconfigurerequest.value_mask & CWX) ?
376                 e->xconfigurerequest.x : client->area.x;
377             y = (e->xconfigurerequest.value_mask & CWY) ?
378                 e->xconfigurerequest.y : client->area.y;
379             w = (e->xconfigurerequest.value_mask & CWWidth) ?
380                 e->xconfigurerequest.width : client->area.width;
381             h = (e->xconfigurerequest.value_mask & CWHeight) ?
382                 e->xconfigurerequest.height : client->area.height;
383                
384             switch (client->gravity) {
385             case NorthEastGravity:
386             case EastGravity:
387                 corner = Corner_TopRight;
388                 break;
389             case SouthWestGravity:
390             case SouthGravity:
391                 corner = Corner_BottomLeft;
392                 break;
393             case SouthEastGravity:
394                 corner = Corner_BottomRight;
395                 break;
396             default:     /* NorthWest, Static, etc */
397                 corner = Corner_TopLeft;
398             }
399
400             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
401         }
402
403         if (e->xconfigurerequest.value_mask & CWStackMode) {
404             switch (e->xconfigurerequest.detail) {
405             case Below:
406             case BottomIf:
407                 stacking_lower(client);
408                 break;
409
410             case Above:
411             case TopIf:
412             default:
413                 stacking_raise(client);
414                 break;
415             }
416         }
417         break;
418     case UnmapNotify:
419         if (client->ignore_unmaps) {
420             client->ignore_unmaps--;
421             break;
422         }
423         g_message("UnmapNotify for %lx", client->window);
424         client_unmanage(client);
425         break;
426     case DestroyNotify:
427         g_message("DestroyNotify for %lx", client->window);
428         client_unmanage(client);
429         break;
430     case ReparentNotify:
431         /* this is when the client is first taken captive in the frame */
432         if (e->xreparent.parent == client->frame->plate) break;
433
434         /*
435           This event is quite rare and is usually handled in unmapHandler.
436           However, if the window is unmapped when the reparent event occurs,
437           the window manager never sees it because an unmap event is not sent
438           to an already unmapped window.
439         */
440
441         /* we don't want the reparent event, put it back on the stack for the
442            X server to deal with after we unmanage the window */
443         XPutBackEvent(ob_display, e);
444      
445         client_unmanage(client);
446         break;
447     case MapRequest:
448         /* we shouldn't be able to get this unless we're iconic */
449         g_assert(client->iconic);
450
451         if (screen_showing_desktop)
452             screen_show_desktop(FALSE);
453         client_iconify(client, FALSE, TRUE);
454         if (!client->frame->visible)
455             /* if its not visible still, then don't mess with it */
456             break;
457         if (client->shaded)
458             client_shade(client, FALSE);
459         client_focus(client);
460         stacking_raise(client);
461         break;
462     case ClientMessage:
463         /* validate cuz we query stuff off the client here */
464         if (!client_validate(client)) break;
465   
466         if (e->xclient.format != 32) return;
467
468         msgtype = e->xclient.message_type;
469         if (msgtype == prop_atoms.wm_change_state) {
470             /* compress changes into a single change */
471             while (XCheckTypedWindowEvent(ob_display, e->type,
472                                           client->window, &ce)) {
473                 /* XXX: it would be nice to compress ALL messages of a
474                    type, not just messages in a row without other
475                    message types between. */
476                 if (ce.xclient.message_type != msgtype) {
477                     XPutBackEvent(ob_display, &ce);
478                     break;
479                 }
480                 e->xclient = ce.xclient;
481             }
482             client_set_wm_state(client, e->xclient.data.l[0]);
483         } else if (msgtype == prop_atoms.net_wm_desktop) {
484             /* compress changes into a single change */
485             while (XCheckTypedWindowEvent(ob_display, e->type,
486                                           client->window, &ce)) {
487                 /* XXX: it would be nice to compress ALL messages of a
488                    type, not just messages in a row without other
489                    message types between. */
490                 if (ce.xclient.message_type != msgtype) {
491                     XPutBackEvent(ob_display, &ce);
492                     break;
493                 }
494                 e->xclient = ce.xclient;
495             }
496             client_set_desktop(client, e->xclient.data.l[0]);
497         } else if (msgtype == prop_atoms.net_wm_state) {
498             /* can't compress these */
499             g_message("net_wm_state %s %ld %ld for 0x%lx\n",
500                       (e->xclient.data.l[0] == 0 ? "Remove" :
501                        e->xclient.data.l[0] == 1 ? "Add" :
502                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
503                       e->xclient.data.l[1], e->xclient.data.l[2],
504                       client->window);
505             client_set_state(client, e->xclient.data.l[0],
506                              e->xclient.data.l[1], e->xclient.data.l[2]);
507         } else if (msgtype == prop_atoms.net_close_window) {
508             g_message("net_close_window for 0x%lx\n", client->window);
509             client_close(client);
510         } else if (msgtype == prop_atoms.net_active_window) {
511             g_message("net_active_window for 0x%lx\n", client->window);
512             if (screen_showing_desktop)
513                 screen_show_desktop(FALSE);
514             if (client->iconic)
515                 client_iconify(client, FALSE, TRUE);
516             else if (!client->frame->visible)
517                 /* if its not visible for other reasons, then don't mess
518                    with it */
519                 break;
520             if (client->shaded)
521                 client_shade(client, FALSE);
522             client_focus(client);
523             stacking_raise(client);
524         }
525         break;
526     case PropertyNotify:
527         /* validate cuz we query stuff off the client here */
528         if (!client_validate(client)) break;
529   
530         /* compress changes to a single property into a single change */
531         while (XCheckTypedWindowEvent(ob_display, e->type,
532                                       client->window, &ce)) {
533             /* XXX: it would be nice to compress ALL changes to a property,
534                not just changes in a row without other props between. */
535             if (ce.xproperty.atom != e->xproperty.atom) {
536                 XPutBackEvent(ob_display, &ce);
537                 break;
538             }
539         }
540
541         msgtype = e->xproperty.atom;
542         if (msgtype == XA_WM_NORMAL_HINTS) {
543             client_update_normal_hints(client);
544             /* normal hints can make a window non-resizable */
545             client_setup_decor_and_functions(client);
546         }
547         else if (msgtype == XA_WM_HINTS)
548             client_update_wmhints(client);
549         else if (msgtype == XA_WM_TRANSIENT_FOR) {
550             client_update_transient_for(client);
551             client_get_type(client);
552             /* type may have changed, so update the layer */
553             client_calc_layer(client);
554             client_setup_decor_and_functions(client);
555         }
556         else if (msgtype == prop_atoms.net_wm_name ||
557                  msgtype == prop_atoms.wm_name)
558             client_update_title(client);
559         else if (msgtype == prop_atoms.net_wm_icon_name ||
560                  msgtype == prop_atoms.wm_icon_name)
561             client_update_icon_title(client);
562         else if (msgtype == prop_atoms.wm_class)
563             client_update_class(client);
564         else if (msgtype == prop_atoms.wm_protocols) {
565             client_update_protocols(client);
566             client_setup_decor_and_functions(client);
567         }
568         else if (msgtype == prop_atoms.net_wm_strut)
569             client_update_strut(client);
570         else if (msgtype == prop_atoms.net_wm_icon)
571             client_update_icons(client);
572         else if (msgtype == prop_atoms.kwm_win_icon)
573             client_update_kwm_icon(client);
574     }
575 }