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