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