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