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