]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
free the themename properly
[dana/openbox.git] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "config.h"
4 #include "xerror.h"
5 #include "prop.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     /* grab the lasttime and hack up the state */
153     switch (e->type) {
154     case ButtonPress:
155     case ButtonRelease:
156         event_lasttime = e->xbutton.time;
157         e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask);
158         /* kill off the Button1Mask etc, only want the modifiers */
159         e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask |
160                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
161         break;
162     case KeyPress:
163         event_lasttime = e->xkey.time;
164         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
165         /* kill off the Button1Mask etc, only want the modifiers */
166         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
167                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
168         /* add to the state the mask of the modifier being pressed, if it is
169            a modifier key being pressed (this is a little ugly..) */
170 /* I'm commenting this out cuz i don't want "C-Control_L" being returned. */
171 /*      kp = modmap->modifiermap;*/
172 /*      for (i = 0; i < mask_table_size; ++i) {*/
173 /*          for (k = 0; k < modmap->max_keypermod; ++k) {*/
174 /*              if (*kp == e->xkey.keycode) {*/ /* found the keycode */
175                     /* add the mask for it */
176 /*                  e->xkey.state |= mask_table[i];*/
177                     /* cause the first loop to break; */
178 /*                  i = mask_table_size;*/
179 /*                  break;*/ /* get outta here! */
180 /*              }*/
181 /*              ++kp;*/
182 /*          }*/
183 /*      }*/
184
185         break;
186     case KeyRelease:
187         event_lasttime = e->xkey.time;
188         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
189         /* kill off the Button1Mask etc, only want the modifiers */
190         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
191                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
192         /* remove from the state the mask of the modifier being released, if
193            it is a modifier key being released (this is a little ugly..) */
194         kp = modmap->modifiermap;
195         for (i = 0; i < mask_table_size; ++i) {
196             for (k = 0; k < modmap->max_keypermod; ++k) {
197                 if (*kp == e->xkey.keycode) { /* found the keycode */
198                     /* remove the mask for it */
199                     e->xkey.state &= ~mask_table[i];
200                     /* cause the first loop to break; */
201                     i = mask_table_size;
202                     break; /* get outta here! */
203                 }
204                 ++kp;
205             }
206         }
207         break;
208     case MotionNotify:
209         event_lasttime = e->xmotion.time;
210         e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask);
211         /* kill off the Button1Mask etc, only want the modifiers */
212         e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask |
213                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
214         /* compress events */
215         while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) {
216             e->xmotion.x_root = ce.xmotion.x_root;
217             e->xmotion.y_root = ce.xmotion.y_root;
218         }
219         break;
220     case PropertyNotify:
221         event_lasttime = e->xproperty.time;
222         break;
223     case FocusIn:
224         g_message("FocusIn on %lx mode %d detail %d", window,
225                   e->xfocus.mode, e->xfocus.detail);
226         if (e->xfocus.detail == NotifyInferior ||
227             e->xfocus.detail == NotifyAncestor ||
228             e->xfocus.detail > NotifyNonlinearVirtual) return;
229             g_message("FocusIn on %lx", window);
230         break;
231     case FocusOut:
232         g_message("FocusOut on %lx mode %d detail %d", window,
233                   e->xfocus.mode, e->xfocus.detail);
234         if (e->xfocus.detail == NotifyInferior ||
235             e->xfocus.detail == NotifyAncestor ||
236             e->xfocus.detail > NotifyNonlinearVirtual) return;
237
238         g_message("FocusOut on %lx", window);
239         /* FocusOut events just make us look for FocusIn events. They
240            are mostly ignored otherwise. */
241         {
242             XEvent fi;
243             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
244                 event_process(&fi);
245
246                 if (fi.xfocus.window == e->xfocus.window)
247                     return;
248                 /* secret magic way of event_process telling us that no client
249                    was found for the FocusIn event */
250                 if (fi.xfocus.window == None)
251                     focus_fallback(FALSE);
252             } else
253                 focus_fallback(FALSE);
254         }
255         break;
256     case EnterNotify:
257     case LeaveNotify:
258         event_lasttime = e->xcrossing.time;
259         /* NotifyUngrab occurs when a mouse button is released and the event is
260            caused, like when lowering a window */
261         if (e->xcrossing.mode == NotifyGrab) return;
262         break;
263     default:
264         event_lasttime = CurrentTime;
265         break;
266     }
267
268     client = g_hash_table_lookup(client_map, &window);
269
270     /* deal with it in the kernel */
271     if (client)
272         event_handle_client(client, e);
273     else if (window == ob_root)
274         event_handle_root(e);
275     else if (e->type == MapRequest)
276         client_manage(window);
277     else if (e->type == FocusIn)
278         e->xfocus.window = None; /* says no client was found for the event */
279     else if (e->type == ConfigureRequest) {
280         /* unhandled configure requests must be used to configure the
281            window directly */
282         XWindowChanges xwc;
283                
284         xwc.x = e->xconfigurerequest.x;
285         xwc.y = e->xconfigurerequest.y;
286         xwc.width = e->xconfigurerequest.width;
287         xwc.height = e->xconfigurerequest.height;
288         xwc.border_width = e->xconfigurerequest.border_width;
289         xwc.sibling = e->xconfigurerequest.above;
290         xwc.stack_mode = e->xconfigurerequest.detail;
291        
292         /* we are not to be held responsible if someone sends us an
293            invalid request! */
294         xerror_set_ignore(TRUE);
295         XConfigureWindow(ob_display, window,
296                          e->xconfigurerequest.value_mask, &xwc);
297         xerror_set_ignore(FALSE);
298     }
299
300     /* dispatch the event to registered handlers */
301     dispatch_x(e, client);
302 }
303
304 static void event_handle_root(XEvent *e)
305 {
306     Atom msgtype;
307      
308     switch(e->type) {
309     case ClientMessage:
310         if (e->xclient.format != 32) break;
311
312         msgtype = e->xclient.message_type;
313         if (msgtype == prop_atoms.net_current_desktop) {
314             unsigned int d = e->xclient.data.l[0];
315             if (d < screen_num_desktops)
316                 screen_set_desktop(d);
317         } else if (msgtype == prop_atoms.net_number_of_desktops) {
318             unsigned int d = e->xclient.data.l[0];
319             if (d > 0)
320                 screen_set_num_desktops(d);
321         } else if (msgtype == prop_atoms.net_showing_desktop) {
322             screen_show_desktop(e->xclient.data.l[0] != 0);
323         }
324         break;
325     case PropertyNotify:
326         if (e->xproperty.atom == prop_atoms.net_desktop_names)
327             screen_update_desktop_names();
328         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
329             screen_update_layout();
330         break;
331     }
332 }
333
334 static void event_handle_client(Client *client, XEvent *e)
335 {
336     XEvent ce;
337     Atom msgtype;
338     int i=0;
339     ConfigValue focus_follow;
340      
341     switch (e->type) {
342     case FocusIn:
343         focus_set_client(client);
344     case FocusOut:
345         g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
346                   client->window);
347         /* focus state can affect the stacking layer */
348         client_calc_layer(client);
349         engine_frame_adjust_focus(client->frame);
350         break;
351     case EnterNotify:
352         if (ob_state == State_Starting) {
353             /* move it to the top of the focus order */
354             guint desktop = client->desktop;
355             if (desktop == DESKTOP_ALL) desktop = screen_desktop;
356             focus_order[desktop] = g_list_remove(focus_order[desktop], client);
357             focus_order[desktop] = g_list_prepend(focus_order[desktop],client);
358         } else {
359             if (!config_get("focusFollowsMouse", Config_Bool, &focus_follow))
360                 g_assert_not_reached();
361             if (focus_follow.bool)
362                 client_focus(client);
363         }
364         break;
365     case ConfigureRequest:
366         /* compress these */
367         while (XCheckTypedWindowEvent(ob_display, client->window,
368                                       ConfigureRequest, &ce)) {
369             ++i;
370             /* XXX if this causes bad things.. we can compress config req's
371                with the same mask. */
372             e->xconfigurerequest.value_mask |=
373                 ce.xconfigurerequest.value_mask;
374             if (ce.xconfigurerequest.value_mask & CWX)
375                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
376             if (ce.xconfigurerequest.value_mask & CWY)
377                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
378             if (ce.xconfigurerequest.value_mask & CWWidth)
379                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
380             if (ce.xconfigurerequest.value_mask & CWHeight)
381                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
382             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
383                 e->xconfigurerequest.border_width =
384                     ce.xconfigurerequest.border_width;
385             if (ce.xconfigurerequest.value_mask & CWStackMode)
386                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
387         }
388         if (i) g_message("Compressed %d Configures", i);
389
390         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
391         if (client->iconic || client->shaded) return;
392
393         if (e->xconfigurerequest.value_mask & CWBorderWidth)
394             client->border_width = e->xconfigurerequest.border_width;
395
396         /* resize, then move, as specified in the EWMH section 7.7 */
397         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
398                                                CWX | CWY)) {
399             int x, y, w, h;
400             Corner corner;
401                
402             x = (e->xconfigurerequest.value_mask & CWX) ?
403                 e->xconfigurerequest.x : client->area.x;
404             y = (e->xconfigurerequest.value_mask & CWY) ?
405                 e->xconfigurerequest.y : client->area.y;
406             w = (e->xconfigurerequest.value_mask & CWWidth) ?
407                 e->xconfigurerequest.width : client->area.width;
408             h = (e->xconfigurerequest.value_mask & CWHeight) ?
409                 e->xconfigurerequest.height : client->area.height;
410                
411             switch (client->gravity) {
412             case NorthEastGravity:
413             case EastGravity:
414                 corner = Corner_TopRight;
415                 break;
416             case SouthWestGravity:
417             case SouthGravity:
418                 corner = Corner_BottomLeft;
419                 break;
420             case SouthEastGravity:
421                 corner = Corner_BottomRight;
422                 break;
423             default:     /* NorthWest, Static, etc */
424                 corner = Corner_TopLeft;
425             }
426
427             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
428         }
429
430         if (e->xconfigurerequest.value_mask & CWStackMode) {
431             switch (e->xconfigurerequest.detail) {
432             case Below:
433             case BottomIf:
434                 stacking_lower(client);
435                 break;
436
437             case Above:
438             case TopIf:
439             default:
440                 stacking_raise(client);
441                 break;
442             }
443         }
444         break;
445     case UnmapNotify:
446         if (client->ignore_unmaps) {
447             client->ignore_unmaps--;
448             break;
449         }
450         client_unmanage(client);
451         break;
452     case DestroyNotify:
453         client_unmanage(client);
454         break;
455     case ReparentNotify:
456         /* this is when the client is first taken captive in the frame */
457         if (e->xreparent.parent == client->frame->plate) break;
458
459         /*
460           This event is quite rare and is usually handled in unmapHandler.
461           However, if the window is unmapped when the reparent event occurs,
462           the window manager never sees it because an unmap event is not sent
463           to an already unmapped window.
464         */
465
466         /* we don't want the reparent event, put it back on the stack for the
467            X server to deal with after we unmanage the window */
468         XPutBackEvent(ob_display, e);
469      
470         client_unmanage(client);
471         break;
472     case MapRequest:
473         if (!client->iconic) break; /* this normally doesn't happen, but if it
474                                        does, we don't want it! */
475         if (screen_showing_desktop)
476             screen_show_desktop(FALSE);
477         client_iconify(client, FALSE, TRUE);
478         if (!client->frame->visible)
479             /* if its not visible still, then don't mess with it */
480             break;
481         if (client->shaded)
482             client_shade(client, FALSE);
483         client_focus(client);
484         stacking_raise(client);
485         break;
486     case ClientMessage:
487         /* validate cuz we query stuff off the client here */
488         if (!client_validate(client)) break;
489   
490         if (e->xclient.format != 32) return;
491
492         msgtype = e->xclient.message_type;
493         if (msgtype == prop_atoms.wm_change_state) {
494             /* compress changes into a single change */
495             while (XCheckTypedWindowEvent(ob_display, e->type,
496                                           client->window, &ce)) {
497                 /* XXX: it would be nice to compress ALL messages of a
498                    type, not just messages in a row without other
499                    message types between. */
500                 if (ce.xclient.message_type != msgtype) {
501                     XPutBackEvent(ob_display, &ce);
502                     break;
503                 }
504                 e->xclient = ce.xclient;
505             }
506             client_set_wm_state(client, e->xclient.data.l[0]);
507         } else if (msgtype == prop_atoms.net_wm_desktop) {
508             /* compress changes into a single change */
509             while (XCheckTypedWindowEvent(ob_display, e->type,
510                                           client->window, &ce)) {
511                 /* XXX: it would be nice to compress ALL messages of a
512                    type, not just messages in a row without other
513                    message types between. */
514                 if (ce.xclient.message_type != msgtype) {
515                     XPutBackEvent(ob_display, &ce);
516                     break;
517                 }
518                 e->xclient = ce.xclient;
519             }
520             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
521                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
522                 client_set_desktop(client, (unsigned)e->xclient.data.l[0]);
523         } else if (msgtype == prop_atoms.net_wm_state) {
524             /* can't compress these */
525             g_message("net_wm_state %s %ld %ld for 0x%lx",
526                       (e->xclient.data.l[0] == 0 ? "Remove" :
527                        e->xclient.data.l[0] == 1 ? "Add" :
528                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
529                       e->xclient.data.l[1], e->xclient.data.l[2],
530                       client->window);
531             client_set_state(client, e->xclient.data.l[0],
532                              e->xclient.data.l[1], e->xclient.data.l[2]);
533         } else if (msgtype == prop_atoms.net_close_window) {
534             g_message("net_close_window for 0x%lx", client->window);
535             client_close(client);
536         } else if (msgtype == prop_atoms.net_active_window) {
537             g_message("net_active_window for 0x%lx", client->window);
538             if (screen_showing_desktop)
539                 screen_show_desktop(FALSE);
540             if (client->iconic)
541                 client_iconify(client, FALSE, TRUE);
542             else if (!client->frame->visible)
543                 /* if its not visible for other reasons, then don't mess
544                    with it */
545                 break;
546             if (client->shaded)
547                 client_shade(client, FALSE);
548             client_focus(client);
549             stacking_raise(client);
550         }
551         break;
552     case PropertyNotify:
553         /* validate cuz we query stuff off the client here */
554         if (!client_validate(client)) break;
555   
556         /* compress changes to a single property into a single change */
557         while (XCheckTypedWindowEvent(ob_display, e->type,
558                                       client->window, &ce)) {
559             /* XXX: it would be nice to compress ALL changes to a property,
560                not just changes in a row without other props between. */
561             if (ce.xproperty.atom != e->xproperty.atom) {
562                 XPutBackEvent(ob_display, &ce);
563                 break;
564             }
565         }
566
567         msgtype = e->xproperty.atom;
568         if (msgtype == XA_WM_NORMAL_HINTS) {
569             client_update_normal_hints(client);
570             /* normal hints can make a window non-resizable */
571             client_setup_decor_and_functions(client);
572         }
573         else if (msgtype == XA_WM_HINTS)
574             client_update_wmhints(client);
575         else if (msgtype == XA_WM_TRANSIENT_FOR) {
576             client_update_transient_for(client);
577             client_get_type(client);
578             /* type may have changed, so update the layer */
579             client_calc_layer(client);
580             client_setup_decor_and_functions(client);
581         }
582         else if (msgtype == prop_atoms.net_wm_name ||
583                  msgtype == prop_atoms.wm_name)
584             client_update_title(client);
585         else if (msgtype == prop_atoms.net_wm_icon_name ||
586                  msgtype == prop_atoms.wm_icon_name)
587             client_update_icon_title(client);
588         else if (msgtype == prop_atoms.wm_class)
589             client_update_class(client);
590         else if (msgtype == prop_atoms.wm_protocols) {
591             client_update_protocols(client);
592             client_setup_decor_and_functions(client);
593         }
594         else if (msgtype == prop_atoms.net_wm_strut)
595             client_update_strut(client);
596         else if (msgtype == prop_atoms.net_wm_icon)
597             client_update_icons(client);
598         else if (msgtype == prop_atoms.kwm_win_icon)
599             client_update_kwm_icon(client);
600     default:
601         ;
602 #ifdef SHAPE
603         if (extensions_shape && e->type == extensions_shape_event_basep) {
604             client->shaped = ((XShapeEvent*)e)->shaped;
605             engine_frame_adjust_shape(client->frame);
606         }
607 #endif
608     }
609 }