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