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