]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
update engine last for focus events
[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         g_print("%s\n", e->type == EnterNotify ? "EnterNotify":"LeaveNotify");
245         break;
246     }
247
248     client = g_hash_table_lookup(client_map, (gpointer)window);
249
250     /* deal with it in the kernel */
251     if (client) {
252         event_handle_client(client, e);
253     } else if (window == ob_root)
254         event_handle_root(e);
255     else if (e->type == ConfigureRequest) {
256         /* unhandled configure requests must be used to configure the
257            window directly */
258         XWindowChanges xwc;
259                
260         xwc.x = e->xconfigurerequest.x;
261         xwc.y = e->xconfigurerequest.y;
262         xwc.width = e->xconfigurerequest.width;
263         xwc.height = e->xconfigurerequest.height;
264         xwc.border_width = e->xconfigurerequest.border_width;
265         xwc.sibling = e->xconfigurerequest.above;
266         xwc.stack_mode = e->xconfigurerequest.detail;
267        
268         g_message("Proxying configure event for 0x%lx", window);
269        
270         /* we are not to be held responsible if someone sends us an
271            invalid request! */
272         xerror_set_ignore(TRUE);
273         XConfigureWindow(ob_display, window,
274                          e->xconfigurerequest.value_mask, &xwc);
275         xerror_set_ignore(FALSE);
276     }
277
278     /* dispatch the event to registered handlers */
279     dispatch_x(e, client);
280 }
281
282 static void event_handle_root(XEvent *e)
283 {
284     Atom msgtype;
285      
286     switch(e->type) {
287     case MapRequest:
288         g_message("MapRequest on root");
289         client_manage(e->xmap.window);
290         break;
291     case ClientMessage:
292         if (e->xclient.format != 32) break;
293
294         msgtype = e->xclient.message_type;
295         if (msgtype == prop_atoms.net_current_desktop) {
296             unsigned int d = e->xclient.data.l[0];
297             if (d <= screen_num_desktops)
298                 screen_set_desktop(d);
299         } else if (msgtype == prop_atoms.net_number_of_desktops) {
300             unsigned int d = e->xclient.data.l[0];
301             if (d > 0)
302                 screen_set_num_desktops(d);
303         } else if (msgtype == prop_atoms.net_showing_desktop) {
304             screen_show_desktop(e->xclient.data.l[0] != 0);
305         }
306         break;
307     case PropertyNotify:
308         if (e->xproperty.atom == prop_atoms.net_desktop_names)
309             screen_update_desktop_names();
310         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
311             screen_update_layout();
312         break;
313     }
314 }
315
316 static void event_handle_client(Client *client, XEvent *e)
317 {
318     XEvent ce;
319     Atom msgtype;
320      
321     switch (e->type) {
322     case FocusIn:
323         if (focus_client != client)
324             focus_set_client(client);
325
326         /* focus state can affect the stacking layer */
327         client_calc_layer(client);
328
329         engine_frame_adjust_focus(client->frame);
330         break;
331     case FocusOut:
332         if (focus_client == client)
333             focus_set_client(NULL);
334
335         /* focus state can affect the stacking layer */
336         client_calc_layer(client);
337
338         engine_frame_adjust_focus(client->frame);
339         break;
340     case ConfigureRequest:
341         g_message("ConfigureRequest for window %lx", client->window);
342         /* compress these */
343         while (XCheckTypedWindowEvent(ob_display, client->window,
344                                       ConfigureRequest, &ce)) {
345             /* XXX if this causes bad things.. we can compress config req's
346                with the same mask. */
347             e->xconfigurerequest.value_mask |=
348                 ce.xconfigurerequest.value_mask;
349             if (ce.xconfigurerequest.value_mask & CWX)
350                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
351             if (ce.xconfigurerequest.value_mask & CWY)
352                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
353             if (ce.xconfigurerequest.value_mask & CWWidth)
354                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
355             if (ce.xconfigurerequest.value_mask & CWHeight)
356                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
357             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
358                 e->xconfigurerequest.border_width =
359                     ce.xconfigurerequest.border_width;
360             if (ce.xconfigurerequest.value_mask & CWStackMode)
361                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
362         }
363
364         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
365         if (client->iconic || client->shaded) return;
366
367         if (e->xconfigurerequest.value_mask & CWBorderWidth)
368             client->border_width = e->xconfigurerequest.border_width;
369
370         /* resize, then move, as specified in the EWMH section 7.7 */
371         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
372                                                CWX | CWY)) {
373             int x, y, w, h;
374             Corner corner;
375                
376             x = (e->xconfigurerequest.value_mask & CWX) ?
377                 e->xconfigurerequest.x : client->area.x;
378             y = (e->xconfigurerequest.value_mask & CWY) ?
379                 e->xconfigurerequest.y : client->area.y;
380             w = (e->xconfigurerequest.value_mask & CWWidth) ?
381                 e->xconfigurerequest.width : client->area.width;
382             h = (e->xconfigurerequest.value_mask & CWHeight) ?
383                 e->xconfigurerequest.height : client->area.height;
384                
385             switch (client->gravity) {
386             case NorthEastGravity:
387             case EastGravity:
388                 corner = Corner_TopRight;
389                 break;
390             case SouthWestGravity:
391             case SouthGravity:
392                 corner = Corner_BottomLeft;
393                 break;
394             case SouthEastGravity:
395                 corner = Corner_BottomRight;
396                 break;
397             default:     /* NorthWest, Static, etc */
398                 corner = Corner_TopLeft;
399             }
400
401             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
402         }
403
404         if (e->xconfigurerequest.value_mask & CWStackMode) {
405             switch (e->xconfigurerequest.detail) {
406             case Below:
407             case BottomIf:
408                 stacking_lower(client);
409                 break;
410
411             case Above:
412             case TopIf:
413             default:
414                 stacking_raise(client);
415                 break;
416             }
417         }
418         break;
419     case UnmapNotify:
420         if (client->ignore_unmaps) {
421             client->ignore_unmaps--;
422             break;
423         }
424         g_message("UnmapNotify for %lx", client->window);
425         client_unmanage(client);
426         break;
427     case DestroyNotify:
428         g_message("DestroyNotify for %lx", client->window);
429         client_unmanage(client);
430         break;
431     case ReparentNotify:
432         /* this is when the client is first taken captive in the frame */
433         if (e->xreparent.parent == client->frame->plate) break;
434
435         /*
436           This event is quite rare and is usually handled in unmapHandler.
437           However, if the window is unmapped when the reparent event occurs,
438           the window manager never sees it because an unmap event is not sent
439           to an already unmapped window.
440         */
441
442         /* we don't want the reparent event, put it back on the stack for the
443            X server to deal with after we unmanage the window */
444         XPutBackEvent(ob_display, e);
445      
446         client_unmanage(client);
447         break;
448     case MapRequest:
449         /* we shouldn't be able to get this unless we're iconic */
450         g_assert(client->iconic);
451
452         if (screen_showing_desktop)
453             screen_show_desktop(FALSE);
454         client_iconify(client, FALSE, TRUE);
455         if (!client->frame->visible)
456             /* if its not visible still, then don't mess with it */
457             break;
458         if (client->shaded)
459             client_shade(client, FALSE);
460         client_focus(client);
461         stacking_raise(client);
462         break;
463     case ClientMessage:
464         /* validate cuz we query stuff off the client here */
465         if (!client_validate(client)) break;
466   
467         if (e->xclient.format != 32) return;
468
469         msgtype = e->xclient.message_type;
470         if (msgtype == prop_atoms.wm_change_state) {
471             /* compress changes into a single change */
472             while (XCheckTypedWindowEvent(ob_display, e->type,
473                                           client->window, &ce)) {
474                 /* XXX: it would be nice to compress ALL messages of a
475                    type, not just messages in a row without other
476                    message types between. */
477                 if (ce.xclient.message_type != msgtype) {
478                     XPutBackEvent(ob_display, &ce);
479                     break;
480                 }
481                 e->xclient = ce.xclient;
482             }
483             client_set_wm_state(client, e->xclient.data.l[0]);
484         } else if (msgtype == prop_atoms.net_wm_desktop) {
485             /* compress changes into a single change */
486             while (XCheckTypedWindowEvent(ob_display, e->type,
487                                           client->window, &ce)) {
488                 /* XXX: it would be nice to compress ALL messages of a
489                    type, not just messages in a row without other
490                    message types between. */
491                 if (ce.xclient.message_type != msgtype) {
492                     XPutBackEvent(ob_display, &ce);
493                     break;
494                 }
495                 e->xclient = ce.xclient;
496             }
497             client_set_desktop(client, e->xclient.data.l[0]);
498         } else if (msgtype == prop_atoms.net_wm_state) {
499             /* can't compress these */
500             g_message("net_wm_state %s %ld %ld for 0x%lx\n",
501                       (e->xclient.data.l[0] == 0 ? "Remove" :
502                        e->xclient.data.l[0] == 1 ? "Add" :
503                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
504                       e->xclient.data.l[1], e->xclient.data.l[2],
505                       client->window);
506             client_set_state(client, e->xclient.data.l[0],
507                              e->xclient.data.l[1], e->xclient.data.l[2]);
508         } else if (msgtype == prop_atoms.net_close_window) {
509             g_message("net_close_window for 0x%lx\n", client->window);
510             client_close(client);
511         } else if (msgtype == prop_atoms.net_active_window) {
512             g_message("net_active_window for 0x%lx\n", client->window);
513             if (screen_showing_desktop)
514                 screen_show_desktop(FALSE);
515             if (client->iconic)
516                 client_iconify(client, FALSE, TRUE);
517             else if (!client->frame->visible)
518                 /* if its not visible for other reasons, then don't mess
519                    with it */
520                 break;
521             if (client->shaded)
522                 client_shade(client, FALSE);
523             client_focus(client);
524             stacking_raise(client);
525         }
526         break;
527     case PropertyNotify:
528         /* validate cuz we query stuff off the client here */
529         if (!client_validate(client)) break;
530   
531         /* compress changes to a single property into a single change */
532         while (XCheckTypedWindowEvent(ob_display, e->type,
533                                       client->window, &ce)) {
534             /* XXX: it would be nice to compress ALL changes to a property,
535                not just changes in a row without other props between. */
536             if (ce.xproperty.atom != e->xproperty.atom) {
537                 XPutBackEvent(ob_display, &ce);
538                 break;
539             }
540         }
541
542         msgtype = e->xproperty.atom;
543         if (msgtype == XA_WM_NORMAL_HINTS) {
544             client_update_normal_hints(client);
545             /* normal hints can make a window non-resizable */
546             client_setup_decor_and_functions(client);
547         }
548         else if (msgtype == XA_WM_HINTS)
549             client_update_wmhints(client);
550         else if (msgtype == XA_WM_TRANSIENT_FOR) {
551             client_update_transient_for(client);
552             client_get_type(client);
553             /* type may have changed, so update the layer */
554             client_calc_layer(client);
555             client_setup_decor_and_functions(client);
556         }
557         else if (msgtype == prop_atoms.net_wm_name ||
558                  msgtype == prop_atoms.wm_name)
559             client_update_title(client);
560         else if (msgtype == prop_atoms.net_wm_icon_name ||
561                  msgtype == prop_atoms.wm_icon_name)
562             client_update_icon_title(client);
563         else if (msgtype == prop_atoms.wm_class)
564             client_update_class(client);
565         else if (msgtype == prop_atoms.wm_protocols) {
566             client_update_protocols(client);
567             client_setup_decor_and_functions(client);
568         }
569         else if (msgtype == prop_atoms.net_wm_strut)
570             client_update_strut(client);
571         else if (msgtype == prop_atoms.net_wm_icon)
572             client_update_icons(client);
573         else if (msgtype == prop_atoms.kwm_win_icon)
574             client_update_kwm_icon(client);
575     }
576 }