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