]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
add right click in client focuses
[mikachu/openbox.git] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "xerror.h"
4 #include "prop.h"
5 #include "config.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     client = g_hash_table_lookup(client_map, &window);
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 #ifdef DEBUG_FOCUS
227         g_message("FocusIn on %lx mode %d detail %d", window,
228                   e->xfocus.mode, e->xfocus.detail);
229 #endif
230         /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
231            because of RevertToPointerRoot. If the focus ends up reverting to
232            pointer root on a workspace change, then the FocusIn event that we
233            want will be of type NotifyAncestor. This situation does not occur
234            for FocusOut, so it is safely ignored there.
235         */
236         if (e->xfocus.detail == NotifyInferior ||
237             e->xfocus.detail > NotifyNonlinearVirtual ||
238             client == NULL) {
239             /* says a client was not found for the event (or a valid FocusIn
240                event was not found.
241             */
242             e->xfocus.window = None;
243             return;
244         }
245
246 #ifdef DEBUG_FOCUS
247         g_message("FocusIn on %lx", window);
248 #endif
249         break;
250     case FocusOut:
251 #ifdef DEBUG_FOCUS
252         g_message("FocusOut on %lx mode %d detail %d", window,
253                   e->xfocus.mode, e->xfocus.detail);
254 #endif
255         if (e->xfocus.mode == NotifyGrab ||
256             e->xfocus.detail == NotifyInferior ||
257             e->xfocus.detail == NotifyAncestor ||
258             e->xfocus.detail > NotifyNonlinearVirtual) return;
259  
260 #ifdef DEBUG_FOCUS
261        g_message("FocusOut on %lx", window);
262 #endif
263         /* Try process a FocusIn first, and if a legit one isn't found, then
264            do the fallback shiznit. */
265         {
266             XEvent fi, fo;
267             gboolean isfo = FALSE;
268
269             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
270                 event_process(&fi);
271
272                 /* when we have gotten a fi/fo pair, then see if there are any
273                    more fo's coming. if there are, then don't fallback just yet
274                 */
275                 if ((isfo = XCheckTypedEvent(ob_display, FocusOut, &fo)))
276                     XPutBackEvent(ob_display, &fo);
277
278                 /* secret magic way of event_process telling us that no client
279                    was found for the FocusIn event. ^_^ */
280                 if (!isfo && fi.xfocus.window == None)
281                     focus_fallback(Fallback_NoFocus);
282                 if (fi.xfocus.window == e->xfocus.window)
283                     return;
284             } else
285                 focus_fallback(Fallback_NoFocus);
286         }
287         break;
288     case EnterNotify:
289     case LeaveNotify:
290         event_lasttime = e->xcrossing.time;
291         /* NotifyUngrab occurs when a mouse button is released and the event is
292            caused, like when lowering a window */
293         if (e->xcrossing.mode == NotifyGrab ||
294             e->xcrossing.detail == NotifyInferior)
295             return;
296         break;
297     default:
298         event_lasttime = CurrentTime;
299         break;
300     }
301
302     /* deal with it in the kernel */
303     if (client)
304         event_handle_client(client, e);
305     else if (window == ob_root)
306         event_handle_root(e);
307     else if (e->type == MapRequest)
308         client_manage(window);
309     else if (e->type == ConfigureRequest) {
310         /* unhandled configure requests must be used to configure the
311            window directly */
312         XWindowChanges xwc;
313                
314         xwc.x = e->xconfigurerequest.x;
315         xwc.y = e->xconfigurerequest.y;
316         xwc.width = e->xconfigurerequest.width;
317         xwc.height = e->xconfigurerequest.height;
318         xwc.border_width = e->xconfigurerequest.border_width;
319         xwc.sibling = e->xconfigurerequest.above;
320         xwc.stack_mode = e->xconfigurerequest.detail;
321        
322         /* we are not to be held responsible if someone sends us an
323            invalid request! */
324         xerror_set_ignore(TRUE);
325         XConfigureWindow(ob_display, window,
326                          e->xconfigurerequest.value_mask, &xwc);
327         xerror_set_ignore(FALSE);
328     }
329
330     /* dispatch the event to registered handlers */
331     dispatch_x(e, client);
332 }
333
334 static void event_handle_root(XEvent *e)
335 {
336     Atom msgtype;
337      
338     switch(e->type) {
339     case ClientMessage:
340         if (e->xclient.format != 32) break;
341
342         msgtype = e->xclient.message_type;
343         if (msgtype == prop_atoms.net_current_desktop) {
344             unsigned int d = e->xclient.data.l[0];
345             if (d < screen_num_desktops)
346                 screen_set_desktop(d);
347         } else if (msgtype == prop_atoms.net_number_of_desktops) {
348             unsigned int d = e->xclient.data.l[0];
349             if (d > 0)
350                 screen_set_num_desktops(d);
351         } else if (msgtype == prop_atoms.net_showing_desktop) {
352             screen_show_desktop(e->xclient.data.l[0] != 0);
353         }
354         break;
355     case PropertyNotify:
356         if (e->xproperty.atom == prop_atoms.net_desktop_names)
357             screen_update_desktop_names();
358         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
359             screen_update_layout();
360         break;
361     }
362 }
363
364 static void event_handle_client(Client *client, XEvent *e)
365 {
366     XEvent ce;
367     Atom msgtype;
368     int i=0;
369      
370     switch (e->type) {
371     case FocusIn:
372         focus_set_client(client);
373     case FocusOut:
374 #ifdef DEBUG_FOCUS
375         g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
376                   client->window);
377 #endif
378         /* focus state can affect the stacking layer */
379         client_calc_layer(client);
380         engine_frame_adjust_focus(client->frame);
381         break;
382     case EnterNotify:
383         if (client_normal(client)) {
384             if (ob_state == State_Starting) {
385                 /* move it to the top of the focus order */
386                 guint desktop = client->desktop;
387                 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
388                 focus_order[desktop] = g_list_remove(focus_order[desktop],
389                                                      client);
390                 focus_order[desktop] = g_list_prepend(focus_order[desktop],
391                                                       client);
392             } else if (config_focus_follow) {
393 #ifdef DEBUG_FOCUS
394                 g_message("EnterNotify on %lx, focusing window",
395                           client->window);
396 #endif
397                 client_focus(client);
398             }
399         }
400         break;
401     case ConfigureRequest:
402         /* compress these */
403         while (XCheckTypedWindowEvent(ob_display, client->window,
404                                       ConfigureRequest, &ce)) {
405             ++i;
406             /* XXX if this causes bad things.. we can compress config req's
407                with the same mask. */
408             e->xconfigurerequest.value_mask |=
409                 ce.xconfigurerequest.value_mask;
410             if (ce.xconfigurerequest.value_mask & CWX)
411                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
412             if (ce.xconfigurerequest.value_mask & CWY)
413                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
414             if (ce.xconfigurerequest.value_mask & CWWidth)
415                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
416             if (ce.xconfigurerequest.value_mask & CWHeight)
417                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
418             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
419                 e->xconfigurerequest.border_width =
420                     ce.xconfigurerequest.border_width;
421             if (ce.xconfigurerequest.value_mask & CWStackMode)
422                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
423         }
424
425         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
426         if (client->iconic || client->shaded) return;
427
428         if (e->xconfigurerequest.value_mask & CWBorderWidth)
429             client->border_width = e->xconfigurerequest.border_width;
430
431         /* resize, then move, as specified in the EWMH section 7.7 */
432         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
433                                                CWX | CWY)) {
434             int x, y, w, h;
435             Corner corner;
436                
437             x = (e->xconfigurerequest.value_mask & CWX) ?
438                 e->xconfigurerequest.x : client->area.x;
439             y = (e->xconfigurerequest.value_mask & CWY) ?
440                 e->xconfigurerequest.y : client->area.y;
441             w = (e->xconfigurerequest.value_mask & CWWidth) ?
442                 e->xconfigurerequest.width : client->area.width;
443             h = (e->xconfigurerequest.value_mask & CWHeight) ?
444                 e->xconfigurerequest.height : client->area.height;
445                
446             switch (client->gravity) {
447             case NorthEastGravity:
448             case EastGravity:
449                 corner = Corner_TopRight;
450                 break;
451             case SouthWestGravity:
452             case SouthGravity:
453                 corner = Corner_BottomLeft;
454                 break;
455             case SouthEastGravity:
456                 corner = Corner_BottomRight;
457                 break;
458             default:     /* NorthWest, Static, etc */
459                 corner = Corner_TopLeft;
460             }
461
462             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
463         }
464
465         if (e->xconfigurerequest.value_mask & CWStackMode) {
466             switch (e->xconfigurerequest.detail) {
467             case Below:
468             case BottomIf:
469                 stacking_lower(client);
470                 break;
471
472             case Above:
473             case TopIf:
474             default:
475                 stacking_raise(client);
476                 break;
477             }
478         }
479         break;
480     case UnmapNotify:
481         if (client->ignore_unmaps) {
482             client->ignore_unmaps--;
483             break;
484         }
485         client_unmanage(client);
486         break;
487     case DestroyNotify:
488         client_unmanage(client);
489         break;
490     case ReparentNotify:
491         /* this is when the client is first taken captive in the frame */
492         if (e->xreparent.parent == client->frame->plate) break;
493
494         /*
495           This event is quite rare and is usually handled in unmapHandler.
496           However, if the window is unmapped when the reparent event occurs,
497           the window manager never sees it because an unmap event is not sent
498           to an already unmapped window.
499         */
500
501         /* we don't want the reparent event, put it back on the stack for the
502            X server to deal with after we unmanage the window */
503         XPutBackEvent(ob_display, e);
504      
505         client_unmanage(client);
506         break;
507     case MapRequest:
508         g_message("MapRequest for 0x%lx", client->window);
509         if (!client->iconic) break; /* this normally doesn't happen, but if it
510                                        does, we don't want it! */
511         if (screen_showing_desktop)
512             screen_show_desktop(FALSE);
513         client_iconify(client, FALSE, TRUE);
514         if (!client->frame->visible)
515             /* if its not visible still, then don't mess with it */
516             break;
517         if (client->shaded)
518             client_shade(client, FALSE);
519         client_focus(client);
520         stacking_raise(client);
521         break;
522     case ClientMessage:
523         /* validate cuz we query stuff off the client here */
524         if (!client_validate(client)) break;
525   
526         if (e->xclient.format != 32) return;
527
528         msgtype = e->xclient.message_type;
529         if (msgtype == prop_atoms.wm_change_state) {
530             /* compress changes into a single change */
531             while (XCheckTypedWindowEvent(ob_display, e->type,
532                                           client->window, &ce)) {
533                 /* XXX: it would be nice to compress ALL messages of a
534                    type, not just messages in a row without other
535                    message types between. */
536                 if (ce.xclient.message_type != msgtype) {
537                     XPutBackEvent(ob_display, &ce);
538                     break;
539                 }
540                 e->xclient = ce.xclient;
541             }
542             client_set_wm_state(client, e->xclient.data.l[0]);
543         } else if (msgtype == prop_atoms.net_wm_desktop) {
544             /* compress changes into a single change */
545             while (XCheckTypedWindowEvent(ob_display, e->type,
546                                           client->window, &ce)) {
547                 /* XXX: it would be nice to compress ALL messages of a
548                    type, not just messages in a row without other
549                    message types between. */
550                 if (ce.xclient.message_type != msgtype) {
551                     XPutBackEvent(ob_display, &ce);
552                     break;
553                 }
554                 e->xclient = ce.xclient;
555             }
556             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
557                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
558                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
559                                    FALSE);
560         } else if (msgtype == prop_atoms.net_wm_state) {
561             /* can't compress these */
562             g_message("net_wm_state %s %ld %ld for 0x%lx",
563                       (e->xclient.data.l[0] == 0 ? "Remove" :
564                        e->xclient.data.l[0] == 1 ? "Add" :
565                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
566                       e->xclient.data.l[1], e->xclient.data.l[2],
567                       client->window);
568             client_set_state(client, e->xclient.data.l[0],
569                              e->xclient.data.l[1], e->xclient.data.l[2]);
570         } else if (msgtype == prop_atoms.net_close_window) {
571             g_message("net_close_window for 0x%lx", client->window);
572             client_close(client);
573         } else if (msgtype == prop_atoms.net_active_window) {
574             g_message("net_active_window for 0x%lx", client->window);
575             if (screen_showing_desktop)
576                 screen_show_desktop(FALSE);
577             if (client->iconic)
578                 client_iconify(client, FALSE, TRUE);
579             else if (!client->frame->visible)
580                 /* if its not visible for other reasons, then don't mess
581                    with it */
582                 break;
583             if (client->shaded)
584                 client_shade(client, FALSE);
585             client_focus(client);
586             stacking_raise(client);
587         }
588         break;
589     case PropertyNotify:
590         /* validate cuz we query stuff off the client here */
591         if (!client_validate(client)) break;
592   
593         /* compress changes to a single property into a single change */
594         while (XCheckTypedWindowEvent(ob_display, e->type,
595                                       client->window, &ce)) {
596             /* XXX: it would be nice to compress ALL changes to a property,
597                not just changes in a row without other props between. */
598             if (ce.xproperty.atom != e->xproperty.atom) {
599                 XPutBackEvent(ob_display, &ce);
600                 break;
601             }
602         }
603
604         msgtype = e->xproperty.atom;
605         if (msgtype == XA_WM_NORMAL_HINTS) {
606             client_update_normal_hints(client);
607             /* normal hints can make a window non-resizable */
608             client_setup_decor_and_functions(client);
609         }
610         else if (msgtype == XA_WM_HINTS)
611             client_update_wmhints(client);
612         else if (msgtype == XA_WM_TRANSIENT_FOR) {
613             client_update_transient_for(client);
614             client_get_type(client);
615             /* type may have changed, so update the layer */
616             client_calc_layer(client);
617             client_setup_decor_and_functions(client);
618         }
619         else if (msgtype == prop_atoms.net_wm_name ||
620                  msgtype == prop_atoms.wm_name)
621             client_update_title(client);
622         else if (msgtype == prop_atoms.net_wm_icon_name ||
623                  msgtype == prop_atoms.wm_icon_name)
624             client_update_icon_title(client);
625         else if (msgtype == prop_atoms.wm_class)
626             client_update_class(client);
627         else if (msgtype == prop_atoms.wm_protocols) {
628             client_update_protocols(client);
629             client_setup_decor_and_functions(client);
630         }
631         else if (msgtype == prop_atoms.net_wm_strut)
632             client_update_strut(client);
633         else if (msgtype == prop_atoms.net_wm_icon)
634             client_update_icons(client);
635         else if (msgtype == prop_atoms.kwm_win_icon)
636             client_update_kwm_icon(client);
637     default:
638         ;
639 #ifdef SHAPE
640         if (extensions_shape && e->type == extensions_shape_event_basep) {
641             client->shaped = ((XShapeEvent*)e)->shaped;
642             engine_frame_adjust_shape(client->frame);
643         }
644 #endif
645     }
646 }