dont need a mask_list here
[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
19 static void event_process(XEvent *e);
20 static void event_handle_root(XEvent *e);
21 static void event_handle_client(Client *c, XEvent *e);
22
23 Time event_lasttime = 0;
24
25 /*! The value of the mask for the NumLock modifier */
26 unsigned int NumLockMask;
27 /*! The value of the mask for the ScrollLock modifier */
28 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
66 void event_shutdown()
67 {
68     XFreeModifiermap(modmap);
69 }
70
71 void event_loop()
72 {
73     fd_set selset;
74     XEvent e;
75     int x_fd;
76     struct timeval *wait;
77
78     while (TRUE) {
79         /*
80           There are slightly different event retrieval semantics here for
81           local (or high bandwidth) versus remote (or low bandwidth)
82           connections to the display/Xserver.
83         */
84         if (ob_remote) {
85             if (!XPending(ob_display))
86                 break;
87         } else {
88             /*
89               This XSync allows for far more compression of events, which
90               makes things like Motion events perform far far better. Since
91               it also means network traffic for every event instead of every
92               X events (where X is the number retrieved at a time), it
93               probably should not be used for setups where Openbox is
94               running on a remote/low bandwidth display/Xserver.
95             */
96             XSync(ob_display, FALSE);
97             if (!XEventsQueued(ob_display, QueuedAlready))
98                 break;
99         }
100         XNextEvent(ob_display, &e);
101
102         event_process(&e);
103     }
104      
105     timer_dispatch((GTimeVal**)&wait);
106     x_fd = ConnectionNumber(ob_display);
107     FD_ZERO(&selset);
108     FD_SET(x_fd, &selset);
109     select(x_fd + 1, &selset, NULL, NULL, wait);
110 }
111
112 void event_process(XEvent *e)
113 {
114     XEvent ce;
115     KeyCode *kp;
116     Window window;
117     int i, k;
118     Client *client;
119
120     /* pick a window */
121     switch (e->type) {
122     case UnmapNotify:
123         window = e->xunmap.window;
124         break;
125     case DestroyNotify:
126         window = e->xdestroywindow.window;
127         break;
128     case ConfigureRequest:
129         window = e->xconfigurerequest.window;
130         break;
131     default:
132         /* XKB events */
133         if (e->type == extensions_xkb_event_basep) {
134             switch (((XkbAnyEvent*)&e)->xkb_type) {
135             case XkbBellNotify:
136                 window = ((XkbBellNotifyEvent*)&e)->window;
137             default:
138                 window = None;
139             }
140         } else
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         /* XXX this caused problems before... but i don't remember why. hah.
241            so back it is. if problems arise again, then try filtering on the
242            detail instead of the mode. */
243         if (e->xcrossing.mode != NotifyNormal) return;
244         break;
245     }
246
247     client = g_hash_table_lookup(client_map, (gpointer)window);
248
249     /* deal with it in the kernel */
250     if (client) {
251         event_handle_client(client, e);
252     } else if (window == ob_root)
253         event_handle_root(e);
254     else if (e->type == ConfigureRequest) {
255         /* unhandled configure requests must be used to configure the
256            window directly */
257         XWindowChanges xwc;
258                
259         xwc.x = e->xconfigurerequest.x;
260         xwc.y = e->xconfigurerequest.y;
261         xwc.width = e->xconfigurerequest.width;
262         xwc.height = e->xconfigurerequest.height;
263         xwc.border_width = e->xconfigurerequest.border_width;
264         xwc.sibling = e->xconfigurerequest.above;
265         xwc.stack_mode = e->xconfigurerequest.detail;
266        
267         g_message("Proxying configure event for 0x%lx", window);
268        
269         /* we are not to be held responsible if someone sends us an
270            invalid request! */
271         xerror_set_ignore(TRUE);
272         XConfigureWindow(ob_display, window,
273                          e->xconfigurerequest.value_mask, &xwc);
274         xerror_set_ignore(FALSE);
275     }
276
277     /* dispatch the event to registered handlers */
278     dispatch_x(e, client);
279 }
280
281 static void event_handle_root(XEvent *e)
282 {
283     Atom msgtype;
284      
285     switch(e->type) {
286     case MapRequest:
287         g_message("MapRequest on root");
288         client_manage(e->xmap.window);
289         break;
290     case ClientMessage:
291         if (e->xclient.format != 32) break;
292
293         msgtype = e->xclient.message_type;
294         if (msgtype == prop_atoms.net_current_desktop) {
295             unsigned int d = e->xclient.data.l[0];
296             if (d <= screen_num_desktops)
297                 screen_set_desktop(d);
298         } else if (msgtype == prop_atoms.net_number_of_desktops) {
299             unsigned int d = e->xclient.data.l[0];
300             if (d > 0)
301                 screen_set_num_desktops(d);
302         } else if (msgtype == prop_atoms.net_showing_desktop) {
303             screen_show_desktop(e->xclient.data.l[0] != 0);
304         }
305         break;
306     case PropertyNotify:
307         if (e->xproperty.atom == prop_atoms.net_desktop_names)
308             screen_update_desktop_names();
309         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
310             screen_update_layout();
311         break;
312     }
313 }
314
315 static void event_handle_client(Client *client, XEvent *e)
316 {
317     XEvent ce;
318     Atom msgtype;
319      
320     switch (e->type) {
321     case FocusIn:
322         client->focused = TRUE;
323         engine_frame_adjust_focus(client->frame);
324
325         /* focus state can affect the stacking layer */
326         client_calc_layer(client);
327
328         if (focus_client != client)
329             focus_set_client(client);
330         break;
331     case FocusOut:
332         client->focused = FALSE;
333         engine_frame_adjust_focus(client->frame);
334
335         /* focus state can affect the stacking layer */
336         client_calc_layer(client);
337
338         if (focus_client == client)
339             focus_set_client(NULL);
340         break;
341     case ConfigureRequest:
342         g_message("ConfigureRequest for window %lx", client->window);
343         /* compress these */
344         while (XCheckTypedWindowEvent(ob_display, client->window,
345                                       ConfigureRequest, &ce)) {
346             /* XXX if this causes bad things.. we can compress config req's
347                with the same mask. */
348             e->xconfigurerequest.value_mask |=
349                 ce.xconfigurerequest.value_mask;
350             if (ce.xconfigurerequest.value_mask & CWX)
351                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
352             if (ce.xconfigurerequest.value_mask & CWY)
353                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
354             if (ce.xconfigurerequest.value_mask & CWWidth)
355                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
356             if (ce.xconfigurerequest.value_mask & CWHeight)
357                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
358             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
359                 e->xconfigurerequest.border_width =
360                     ce.xconfigurerequest.border_width;
361             if (ce.xconfigurerequest.value_mask & CWStackMode)
362                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
363         }
364
365         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
366         if (client->iconic || client->shaded) return;
367
368         if (e->xconfigurerequest.value_mask & CWBorderWidth)
369             client->border_width = e->xconfigurerequest.border_width;
370
371         /* resize, then move, as specified in the EWMH section 7.7 */
372         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
373                                                CWX | CWY)) {
374             int x, y, w, h;
375             Corner corner;
376                
377             x = (e->xconfigurerequest.value_mask & CWX) ?
378                 e->xconfigurerequest.x : client->area.x;
379             y = (e->xconfigurerequest.value_mask & CWY) ?
380                 e->xconfigurerequest.y : client->area.y;
381             w = (e->xconfigurerequest.value_mask & CWWidth) ?
382                 e->xconfigurerequest.width : client->area.width;
383             h = (e->xconfigurerequest.value_mask & CWHeight) ?
384                 e->xconfigurerequest.height : client->area.height;
385                
386             switch (client->gravity) {
387             case NorthEastGravity:
388             case EastGravity:
389                 corner = Corner_TopRight;
390                 break;
391             case SouthWestGravity:
392             case SouthGravity:
393                 corner = Corner_BottomLeft;
394                 break;
395             case SouthEastGravity:
396                 corner = Corner_BottomRight;
397                 break;
398             default:     /* NorthWest, Static, etc */
399                 corner = Corner_TopLeft;
400             }
401
402             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
403         }
404
405         if (e->xconfigurerequest.value_mask & CWStackMode) {
406             switch (e->xconfigurerequest.detail) {
407             case Below:
408             case BottomIf:
409                 stacking_lower(client);
410                 break;
411
412             case Above:
413             case TopIf:
414             default:
415                 stacking_raise(client);
416                 break;
417             }
418         }
419         break;
420     case UnmapNotify:
421         if (client->ignore_unmaps) {
422             client->ignore_unmaps--;
423             break;
424         }
425         g_message("UnmapNotify for %lx", client->window);
426         client_unmanage(client);
427         break;
428     case DestroyNotify:
429         g_message("DestroyNotify for %lx", client->window);
430         client_unmanage(client);
431         break;
432     case ReparentNotify:
433         /* this is when the client is first taken captive in the frame */
434         if (e->xreparent.parent == client->frame->plate) break;
435
436         /*
437           This event is quite rare and is usually handled in unmapHandler.
438           However, if the window is unmapped when the reparent event occurs,
439           the window manager never sees it because an unmap event is not sent
440           to an already unmapped window.
441         */
442
443         /* we don't want the reparent event, put it back on the stack for the
444            X server to deal with after we unmanage the window */
445         XPutBackEvent(ob_display, e);
446      
447         client_unmanage(client);
448         break;
449     case MapRequest:
450         /* we shouldn't be able to get this unless we're iconic */
451         g_assert(client->iconic);
452
453         /*HOOKFIRECLIENT(requestactivate, client);XXX*/
454         break;
455     case ClientMessage:
456         /* validate cuz we query stuff off the client here */
457         if (!client_validate(client)) break;
458   
459         if (e->xclient.format != 32) return;
460
461         msgtype = e->xclient.message_type;
462         if (msgtype == prop_atoms.wm_change_state) {
463             /* compress changes into a single change */
464             while (XCheckTypedWindowEvent(ob_display, e->type,
465                                           client->window, &ce)) {
466                 /* XXX: it would be nice to compress ALL messages of a
467                    type, not just messages in a row without other
468                    message types between. */
469                 if (ce.xclient.message_type != msgtype) {
470                     XPutBackEvent(ob_display, &ce);
471                     break;
472                 }
473                 e->xclient = ce.xclient;
474             }
475             client_set_wm_state(client, e->xclient.data.l[0]);
476         } else if (msgtype == prop_atoms.net_wm_desktop) {
477             /* compress changes into a single change */
478             while (XCheckTypedWindowEvent(ob_display, e->type,
479                                           client->window, &ce)) {
480                 /* XXX: it would be nice to compress ALL messages of a
481                    type, not just messages in a row without other
482                    message types between. */
483                 if (ce.xclient.message_type != msgtype) {
484                     XPutBackEvent(ob_display, &ce);
485                     break;
486                 }
487                 e->xclient = ce.xclient;
488             }
489             client_set_desktop(client, e->xclient.data.l[0]);
490         } else if (msgtype == prop_atoms.net_wm_state) {
491             /* can't compress these */
492             g_message("net_wm_state %s %ld %ld for 0x%lx\n",
493                       (e->xclient.data.l[0] == 0 ? "Remove" :
494                        e->xclient.data.l[0] == 1 ? "Add" :
495                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
496                       e->xclient.data.l[1], e->xclient.data.l[2],
497                       client->window);
498             client_set_state(client, e->xclient.data.l[0],
499                              e->xclient.data.l[1], e->xclient.data.l[2]);
500         } else if (msgtype == prop_atoms.net_close_window) {
501             g_message("net_close_window for 0x%lx\n", client->window);
502             client_close(client);
503         } else if (msgtype == prop_atoms.net_active_window) {
504             g_message("net_active_window for 0x%lx\n", client->window);
505             if (screen_showing_desktop)
506                 screen_show_desktop(FALSE);
507             if (client->iconic)
508                 client_iconify(client, FALSE, TRUE);
509             else if (!client->frame->visible)
510                 /* if its not visible for other reasons, then don't mess
511                    with it */
512                 break;
513             if (client->shaded)
514                 client_shade(client, FALSE);
515             client_focus(client);
516             stacking_raise(client);
517         }
518         break;
519     case PropertyNotify:
520         /* validate cuz we query stuff off the client here */
521         if (!client_validate(client)) break;
522   
523         /* compress changes to a single property into a single change */
524         while (XCheckTypedWindowEvent(ob_display, e->type,
525                                       client->window, &ce)) {
526             /* XXX: it would be nice to compress ALL changes to a property,
527                not just changes in a row without other props between. */
528             if (ce.xproperty.atom != e->xproperty.atom) {
529                 XPutBackEvent(ob_display, &ce);
530                 break;
531             }
532         }
533
534         msgtype = e->xproperty.atom;
535         if (msgtype == XA_WM_NORMAL_HINTS) {
536             client_update_normal_hints(client);
537             /* normal hints can make a window non-resizable */
538             client_setup_decor_and_functions(client);
539         }
540         else if (msgtype == XA_WM_HINTS)
541             client_update_wmhints(client);
542         else if (msgtype == XA_WM_TRANSIENT_FOR) {
543             client_update_transient_for(client);
544             client_get_type(client);
545             /* type may have changed, so update the layer */
546             client_calc_layer(client);
547             client_setup_decor_and_functions(client);
548         }
549         else if (msgtype == prop_atoms.net_wm_name ||
550                  msgtype == prop_atoms.wm_name)
551             client_update_title(client);
552         else if (msgtype == prop_atoms.net_wm_icon_name ||
553                  msgtype == prop_atoms.wm_icon_name)
554             client_update_icon_title(client);
555         else if (msgtype == prop_atoms.wm_class)
556             client_update_class(client);
557         else if (msgtype == prop_atoms.wm_protocols) {
558             client_update_protocols(client);
559             client_setup_decor_and_functions(client);
560         }
561         else if (msgtype == prop_atoms.net_wm_strut)
562             client_update_strut(client);
563         else if (msgtype == prop_atoms.net_wm_icon)
564             client_update_icons(client);
565         else if (msgtype == prop_atoms.kwm_win_icon)
566             client_update_kwm_icon(client);
567     }
568 }