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