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