]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
use a client destructor to watch for focus_in/out becoming invalid
[dana/openbox.git] / openbox / event.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    event.c for the Openbox window manager
4    Copyright (c) 2003        Ben Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "event.h"
20 #include "debug.h"
21 #include "window.h"
22 #include "openbox.h"
23 #include "dock.h"
24 #include "client.h"
25 #include "xerror.h"
26 #include "prop.h"
27 #include "config.h"
28 #include "screen.h"
29 #include "frame.h"
30 #include "menu.h"
31 #include "menuframe.h"
32 #include "keyboard.h"
33 #include "mouse.h"
34 #include "mainloop.h"
35 #include "framerender.h"
36 #include "focus.h"
37 #include "moveresize.h"
38 #include "group.h"
39 #include "stacking.h"
40 #include "extensions.h"
41
42 #include <X11/Xlib.h>
43 #include <X11/keysym.h>
44 #include <X11/Xatom.h>
45 #include <glib.h>
46
47 #ifdef HAVE_SYS_SELECT_H
48 #  include <sys/select.h>
49 #endif
50 #ifdef HAVE_SIGNAL_H
51 #  include <signal.h>
52 #endif
53
54 #ifdef USE_SM
55 #include <X11/ICE/ICElib.h>
56 #endif
57
58 static void event_process(const XEvent *e, gpointer data);
59 static void event_done(gpointer data);
60 static void event_client_dest(ObClient *client, gpointer data);
61 static void event_handle_root(XEvent *e);
62 static void event_handle_menu(XEvent *e);
63 static void event_handle_dock(ObDock *s, XEvent *e);
64 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
65 static void event_handle_client(ObClient *c, XEvent *e);
66 static void event_handle_group(ObGroup *g, XEvent *e);
67
68 static gboolean focus_delay_func(gpointer data);
69 static void focus_delay_client_dest(ObClient *client, gpointer data);
70
71 static gboolean menu_hide_delay_func(gpointer data);
72
73 Time event_lasttime = 0;
74
75 /*! The value of the mask for the NumLock modifier */
76 unsigned int NumLockMask;
77 /*! The value of the mask for the ScrollLock modifier */
78 unsigned int ScrollLockMask;
79 /*! The key codes for the modifier keys */
80 static XModifierKeymap *modmap;
81 /*! Table of the constant modifier masks */
82 static const int mask_table[] = {
83     ShiftMask, LockMask, ControlMask, Mod1Mask,
84     Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
85 };
86 static int mask_table_size;
87
88 static guint ignore_enter_focus = 0;
89
90 static gboolean menu_can_hide;
91
92 static ObClient *focus_in, *focus_out;
93
94 #ifdef USE_SM
95 static void ice_handler(int fd, gpointer conn)
96 {
97     Bool b;
98     IceProcessMessages(conn, NULL, &b);
99 }
100
101 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
102                       IcePointer *watch_data)
103 {
104     static gint fd = -1;
105
106     if (opening) {
107         fd = IceConnectionNumber(conn);
108         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
109     } else {
110         ob_main_loop_fd_remove(ob_main_loop, fd);
111         fd = -1;
112     }
113 }
114 #endif
115
116 void event_startup(gboolean reconfig)
117 {
118     if (reconfig) return;
119
120     mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
121      
122     /* get lock masks that are defined by the display (not constant) */
123     modmap = XGetModifierMapping(ob_display);
124     g_assert(modmap);
125     if (modmap && modmap->max_keypermod > 0) {
126         size_t cnt;
127         const size_t size = mask_table_size * modmap->max_keypermod;
128         /* get the values of the keyboard lock modifiers
129            Note: Caps lock is not retrieved the same way as Scroll and Num
130            lock since it doesn't need to be. */
131         const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
132         const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
133                                                      XK_Scroll_Lock);
134           
135         for (cnt = 0; cnt < size; ++cnt) {
136             if (! modmap->modifiermap[cnt]) continue;
137                
138             if (num_lock == modmap->modifiermap[cnt])
139                 NumLockMask = mask_table[cnt / modmap->max_keypermod];
140             if (scroll_lock == modmap->modifiermap[cnt])
141                 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
142         }
143     }
144
145     ob_main_loop_x_add(ob_main_loop, event_process, event_done, NULL, NULL);
146
147 #ifdef USE_SM
148     IceAddConnectionWatch(ice_watch, NULL);
149 #endif
150
151     client_add_destructor(focus_delay_client_dest, NULL);
152     client_add_destructor(event_client_dest, NULL);
153 }
154
155 void event_shutdown(gboolean reconfig)
156 {
157     if (reconfig) return;
158
159 #ifdef USE_SM
160     IceRemoveConnectionWatch(ice_watch, NULL);
161 #endif
162
163     client_remove_destructor(focus_delay_client_dest);
164     XFreeModifiermap(modmap);
165 }
166
167 static Window event_get_window(XEvent *e)
168 {
169     Window window;
170
171     /* pick a window */
172     switch (e->type) {
173     case SelectionClear:
174         window = RootWindow(ob_display, ob_screen);
175         break;
176     case MapRequest:
177         window = e->xmap.window;
178         break;
179     case UnmapNotify:
180         window = e->xunmap.window;
181         break;
182     case DestroyNotify:
183         window = e->xdestroywindow.window;
184         break;
185     case ConfigureRequest:
186         window = e->xconfigurerequest.window;
187         break;
188     case ConfigureNotify:
189         window = e->xconfigure.window;
190         break;
191     default:
192 #ifdef XKB
193         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
194             switch (((XkbAnyEvent*)e)->xkb_type) {
195             case XkbBellNotify:
196                 window = ((XkbBellNotifyEvent*)e)->window;
197             default:
198                 window = None;
199             }
200         } else
201 #endif
202             window = e->xany.window;
203     }
204     return window;
205 }
206
207 static void event_set_lasttime(XEvent *e)
208 {
209     Time t = 0;
210
211     /* grab the lasttime and hack up the state */
212     switch (e->type) {
213     case ButtonPress:
214     case ButtonRelease:
215         t = e->xbutton.time;
216         break;
217     case KeyPress:
218         t = e->xkey.time;
219         break;
220     case KeyRelease:
221         t = e->xkey.time;
222         break;
223     case MotionNotify:
224         t = e->xmotion.time;
225         break;
226     case PropertyNotify:
227         t = e->xproperty.time;
228         break;
229     case EnterNotify:
230     case LeaveNotify:
231         t = e->xcrossing.time;
232         break;
233     default:
234         /* if more event types are anticipated, get their timestamp
235            explicitly */
236         break;
237     }
238
239     if (t > event_lasttime)
240         event_lasttime = t;
241 }
242
243 #define STRIP_MODS(s) \
244         s &= ~(LockMask | NumLockMask | ScrollLockMask), \
245         /* kill off the Button1Mask etc, only want the modifiers */ \
246         s &= (ControlMask | ShiftMask | Mod1Mask | \
247               Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
248
249 static void event_hack_mods(XEvent *e)
250 {
251     KeyCode *kp;
252     int i, k;
253
254     switch (e->type) {
255     case ButtonPress:
256     case ButtonRelease:
257         STRIP_MODS(e->xbutton.state);
258         break;
259     case KeyPress:
260         STRIP_MODS(e->xkey.state);
261         break;
262     case KeyRelease:
263         STRIP_MODS(e->xkey.state);
264         /* remove from the state the mask of the modifier being released, if
265            it is a modifier key being released (this is a little ugly..) */
266         kp = modmap->modifiermap;
267         for (i = 0; i < mask_table_size; ++i) {
268             for (k = 0; k < modmap->max_keypermod; ++k) {
269                 if (*kp == e->xkey.keycode) { /* found the keycode */
270                     /* remove the mask for it */
271                     e->xkey.state &= ~mask_table[i];
272                     /* cause the first loop to break; */
273                     i = mask_table_size;
274                     break; /* get outta here! */
275                 }
276                 ++kp;
277             }
278         }
279         break;
280     case MotionNotify:
281         STRIP_MODS(e->xmotion.state);
282         /* compress events */
283         {
284             XEvent ce;
285             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
286                                           e->type, &ce)) {
287                 e->xmotion.x_root = ce.xmotion.x_root;
288                 e->xmotion.y_root = ce.xmotion.y_root;
289             }
290         }
291         break;
292     }
293 }
294
295 static gboolean event_ignore(XEvent *e, ObClient *client)
296 {
297     switch(e->type) {
298     case EnterNotify:
299     case LeaveNotify:
300         if (e->xcrossing.detail == NotifyInferior)
301             return TRUE;
302         break;
303     case FocusIn:
304         if (e->xfocus.detail > NotifyNonlinearVirtual)
305             return TRUE;
306         break;
307     case FocusOut:
308         if (e->xfocus.detail > NotifyNonlinearVirtual)
309             return TRUE;
310         if (e->xfocus.detail == NotifyInferior ||
311             e->xfocus.mode == NotifyGrab)
312             return TRUE;
313         break;
314     }
315     return FALSE;
316 }
317
318 static void event_client_dest(ObClient *client, gpointer data)
319 {
320     if (client == focus_in)
321         focus_in = NULL;
322     if (client == focus_out)
323         focus_out = NULL;
324 }
325
326 static void event_done(gpointer data)
327 {
328     static ObClient *last = NULL;
329
330     if (focus_in) {
331         if (focus_in != focus_client) {
332             focus_set_client(focus_in);
333             frame_adjust_focus(focus_in->frame, TRUE);
334             client_calc_layer(focus_in);
335         }
336     } 
337     if (focus_out) {
338         if (focus_out == focus_client)
339             focus_set_client(NULL);
340         frame_adjust_focus(focus_out->frame, FALSE);
341         client_calc_layer(focus_out);
342     }
343
344     if (focus_client != last) {
345         if (!focus_client)
346             focus_fallback(OB_FOCUS_FALLBACK_NOFOCUS);
347         last = focus_client;
348     }
349
350     focus_in = focus_out = NULL;
351 }
352
353 static void event_process(const XEvent *ec, gpointer data)
354 {
355     Window window;
356     ObGroup *group = NULL;
357     ObClient *client = NULL;
358     ObDock *dock = NULL;
359     ObDockApp *dockapp = NULL;
360     ObWindow *obwin = NULL;
361     XEvent ee, *e;
362
363     /* make a copy we can mangle */
364     ee = *ec;
365     e = &ee;
366
367     window = event_get_window(e);
368     if (!(e->type == PropertyNotify &&
369           (group = g_hash_table_lookup(group_map, &window))))
370         if ((obwin = g_hash_table_lookup(window_map, &window))) {
371             switch (obwin->type) {
372             case Window_Dock:
373                 dock = WINDOW_AS_DOCK(obwin);
374                 break;
375             case Window_DockApp:
376                 dockapp = WINDOW_AS_DOCKAPP(obwin);
377                 break;
378             case Window_Client:
379                 client = WINDOW_AS_CLIENT(obwin);
380                 break;
381             case Window_Menu:
382             case Window_Internal:
383                 /* not to be used for events */
384                 g_assert_not_reached();
385                 break;
386             }
387         }
388
389     event_set_lasttime(e);
390     event_hack_mods(e);
391     if (event_ignore(e, client))
392         return;
393
394     /* deal with it in the kernel */
395     if (group)
396         event_handle_group(group, e);
397     else if (client)
398         event_handle_client(client, e);
399     else if (dockapp)
400         event_handle_dockapp(dockapp, e);
401     else if (dock)
402         event_handle_dock(dock, e);
403     else if (window == RootWindow(ob_display, ob_screen))
404         event_handle_root(e);
405     else if (e->type == MapRequest)
406         client_manage(window);
407     else if (e->type == ConfigureRequest) {
408         /* unhandled configure requests must be used to configure the
409            window directly */
410         XWindowChanges xwc;
411                
412         xwc.x = e->xconfigurerequest.x;
413         xwc.y = e->xconfigurerequest.y;
414         xwc.width = e->xconfigurerequest.width;
415         xwc.height = e->xconfigurerequest.height;
416         xwc.border_width = e->xconfigurerequest.border_width;
417         xwc.sibling = e->xconfigurerequest.above;
418         xwc.stack_mode = e->xconfigurerequest.detail;
419        
420         /* we are not to be held responsible if someone sends us an
421            invalid request! */
422         xerror_set_ignore(TRUE);
423         XConfigureWindow(ob_display, window,
424                          e->xconfigurerequest.value_mask, &xwc);
425         xerror_set_ignore(FALSE);
426     }
427
428     /* user input (action-bound) events */
429     if (e->type == ButtonPress || e->type == ButtonRelease ||
430         e->type == MotionNotify || e->type == KeyPress ||
431         e->type == KeyRelease)
432     {
433         if (menu_frame_visible)
434             event_handle_menu(e);
435         else {
436             if (!keyboard_process_interactive_grab(e, &client)) {
437                 if (moveresize_in_progress) {
438                     moveresize_event(e);
439
440                     /* make further actions work on the client being
441                        moved/resized */
442                     client = moveresize_client;
443                 }
444
445                 menu_can_hide = FALSE;
446                 ob_main_loop_timeout_add(ob_main_loop,
447                                          G_USEC_PER_SEC / 4,
448                                          menu_hide_delay_func,
449                                          NULL, NULL);
450
451                 if (e->type == ButtonPress || e->type == ButtonRelease ||
452                     e->type == MotionNotify)
453                     mouse_event(client, e);
454                 else if (e->type == KeyPress)
455                     /* when in the middle of a focus cycling action, this
456                        causes the window which appears to be focused to be
457                        the one on which the actions will be executed */
458                     keyboard_event((focus_cycle_target ?
459                                     focus_cycle_target : client), e);
460             }
461         }
462     }
463 }
464
465 static void event_handle_root(XEvent *e)
466 {
467     Atom msgtype;
468      
469     switch(e->type) {
470     case SelectionClear:
471         ob_debug("Another WM has requested to replace us. Exiting.\n");
472         ob_exit(0);
473         break;
474
475     case ClientMessage:
476         if (e->xclient.format != 32) break;
477
478         msgtype = e->xclient.message_type;
479         if (msgtype == prop_atoms.net_current_desktop) {
480             unsigned int d = e->xclient.data.l[0];
481             if (d < screen_num_desktops)
482                 screen_set_desktop(d);
483         } else if (msgtype == prop_atoms.net_number_of_desktops) {
484             unsigned int d = e->xclient.data.l[0];
485             if (d > 0)
486                 screen_set_num_desktops(d);
487         } else if (msgtype == prop_atoms.net_showing_desktop) {
488             screen_show_desktop(e->xclient.data.l[0] != 0);
489         }
490         break;
491     case PropertyNotify:
492         if (e->xproperty.atom == prop_atoms.net_desktop_names)
493             screen_update_desktop_names();
494         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
495             screen_update_layout();
496         break;
497     case ConfigureNotify:
498 #ifdef XRANDR
499         XRRUpdateConfiguration(e);
500 #endif
501         screen_resize();
502         break;
503     default:
504         ;
505 #ifdef VIDMODE
506         if (extensions_vidmode && e->type == extensions_vidmode_event_basep) {
507             ob_debug("VIDMODE EVENT\n");
508         }
509 #endif
510     }
511 }
512
513 static void event_handle_group(ObGroup *group, XEvent *e)
514 {
515     GSList *it;
516
517     g_assert(e->type == PropertyNotify);
518
519     for (it = group->members; it; it = g_slist_next(it))
520         event_handle_client(it->data, e);
521 }
522
523 void event_enter_client(ObClient *client)
524 {
525     g_assert(config_focus_follow);
526
527     if (client_normal(client) && client_can_focus(client)) {
528         if (config_focus_delay) {
529             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
530             ob_main_loop_timeout_add(ob_main_loop,
531                                      config_focus_delay,
532                                      focus_delay_func,
533                                      client, NULL);
534         } else
535             focus_delay_func(client);
536     }
537 }
538
539 static void event_handle_client(ObClient *client, XEvent *e)
540 {
541     XEvent ce;
542     Atom msgtype;
543     int i=0;
544     ObFrameContext con;
545      
546     switch (e->type) {
547     case VisibilityNotify:
548         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
549         break;
550     case ButtonPress:
551     case ButtonRelease:
552         /* Wheel buttons don't draw because they are an instant click, so it
553            is a waste of resources to go drawing it. */
554         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
555             con = frame_context(client, e->xbutton.window);
556             con = mouse_button_frame_context(con, e->xbutton.button);
557             switch (con) {
558             case OB_FRAME_CONTEXT_MAXIMIZE:
559                 client->frame->max_press = (e->type == ButtonPress);
560                 framerender_frame(client->frame);
561                 break;
562             case OB_FRAME_CONTEXT_CLOSE:
563                 client->frame->close_press = (e->type == ButtonPress);
564                 framerender_frame(client->frame);
565                 break;
566             case OB_FRAME_CONTEXT_ICONIFY:
567                 client->frame->iconify_press = (e->type == ButtonPress);
568                 framerender_frame(client->frame);
569                 break;
570             case OB_FRAME_CONTEXT_ALLDESKTOPS:
571                 client->frame->desk_press = (e->type == ButtonPress);
572                 framerender_frame(client->frame);
573                 break; 
574             case OB_FRAME_CONTEXT_SHADE:
575                 client->frame->shade_press = (e->type == ButtonPress);
576                 framerender_frame(client->frame);
577                 break;
578             default:
579                 /* nothing changes with clicks for any other contexts */
580                 break;
581             }
582         }
583         break;
584     case FocusIn:
585 #ifdef DEBUG_FOCUS
586         ob_debug("FocusIn on client for %lx (client %lx) mode %d detail %d\n",
587                  e->xfocus.window, client->window, e->xfocus.mode, e->xfocus.detail);
588 #endif
589         focus_in = client;
590         if (focus_out == client)
591             focus_out = NULL;
592         break;
593     case FocusOut:
594 #ifdef DEBUG_FOCUS
595         ob_debug("FocusOut on client for %lx (client %lx) mode %d detail %d\n",
596                  e->xfocus.window, client->window, e->xfocus.mode, e->xfocus.detail);
597 #endif
598         if (focus_in == client)
599             focus_in = NULL;
600         if (client == focus_client)
601             focus_out = client;
602         break;
603     case LeaveNotify:
604         con = frame_context(client, e->xcrossing.window);
605         switch (con) {
606         case OB_FRAME_CONTEXT_MAXIMIZE:
607             client->frame->max_hover = FALSE;
608             frame_adjust_state(client->frame);
609             break;
610         case OB_FRAME_CONTEXT_ALLDESKTOPS:
611             client->frame->desk_hover = FALSE;
612             frame_adjust_state(client->frame);
613             break;
614         case OB_FRAME_CONTEXT_SHADE:
615             client->frame->shade_hover = FALSE;
616             frame_adjust_state(client->frame);
617             break;
618         case OB_FRAME_CONTEXT_ICONIFY:
619             client->frame->iconify_hover = FALSE;
620             frame_adjust_state(client->frame);
621             break;
622         case OB_FRAME_CONTEXT_CLOSE:
623             client->frame->close_hover = FALSE;
624             frame_adjust_state(client->frame);
625             break;
626         case OB_FRAME_CONTEXT_FRAME:
627             /*
628             if (config_focus_follow && config_focus_delay)
629                 ob_main_loop_timeout_remove_data(ob_main_loop,
630                                                  focus_delay_func,
631                                                  client);
632             */
633             break;
634         default:
635             break;
636         }
637         break;
638     case EnterNotify:
639     {
640         gboolean nofocus = FALSE;
641
642         if (ignore_enter_focus) {
643             ignore_enter_focus--;
644             nofocus = TRUE;
645         }
646
647         con = frame_context(client, e->xcrossing.window);
648         switch (con) {
649         case OB_FRAME_CONTEXT_MAXIMIZE:
650             client->frame->max_hover = TRUE;
651             frame_adjust_state(client->frame);
652             break;
653         case OB_FRAME_CONTEXT_ALLDESKTOPS:
654             client->frame->desk_hover = TRUE;
655             frame_adjust_state(client->frame);
656             break;
657         case OB_FRAME_CONTEXT_SHADE:
658             client->frame->shade_hover = TRUE;
659             frame_adjust_state(client->frame);
660             break;
661         case OB_FRAME_CONTEXT_ICONIFY:
662             client->frame->iconify_hover = TRUE;
663             frame_adjust_state(client->frame);
664             break;
665         case OB_FRAME_CONTEXT_CLOSE:
666             client->frame->close_hover = TRUE;
667             frame_adjust_state(client->frame);
668             break;
669         case OB_FRAME_CONTEXT_FRAME:
670             if (e->xcrossing.mode == NotifyGrab ||
671                 e->xcrossing.mode == NotifyUngrab)
672             {
673 #ifdef DEBUG_FOCUS
674                 ob_debug("%sNotify mode %d detail %d on %lx IGNORED\n",
675                          (e->type == EnterNotify ? "Enter" : "Leave"),
676                          e->xcrossing.mode,
677                          e->xcrossing.detail, client?client->window:0);
678 #endif
679             } else {
680 #ifdef DEBUG_FOCUS
681                 ob_debug("%sNotify mode %d detail %d on %lx, "
682                          "focusing window\n",
683                          (e->type == EnterNotify ? "Enter" : "Leave"),
684                          e->xcrossing.mode,
685                          e->xcrossing.detail, client?client->window:0);
686 #endif
687                 if (!nofocus && config_focus_follow)
688                     event_enter_client(client);
689             }
690             break;
691         default:
692             break;
693         }
694         break;
695     }
696     case ConfigureRequest:
697         /* compress these */
698         while (XCheckTypedWindowEvent(ob_display, client->window,
699                                       ConfigureRequest, &ce)) {
700             ++i;
701             /* XXX if this causes bad things.. we can compress config req's
702                with the same mask. */
703             e->xconfigurerequest.value_mask |=
704                 ce.xconfigurerequest.value_mask;
705             if (ce.xconfigurerequest.value_mask & CWX)
706                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
707             if (ce.xconfigurerequest.value_mask & CWY)
708                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
709             if (ce.xconfigurerequest.value_mask & CWWidth)
710                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
711             if (ce.xconfigurerequest.value_mask & CWHeight)
712                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
713             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
714                 e->xconfigurerequest.border_width =
715                     ce.xconfigurerequest.border_width;
716             if (ce.xconfigurerequest.value_mask & CWStackMode)
717                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
718         }
719
720         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
721         if (client->iconic || client->shaded) return;
722
723         /* resize, then move, as specified in the EWMH section 7.7 */
724         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
725                                                CWX | CWY |
726                                                CWBorderWidth)) {
727             int x, y, w, h;
728             ObCorner corner;
729
730             if (e->xconfigurerequest.value_mask & CWBorderWidth)
731                 client->border_width = e->xconfigurerequest.border_width;
732
733             x = (e->xconfigurerequest.value_mask & CWX) ?
734                 e->xconfigurerequest.x : client->area.x;
735             y = (e->xconfigurerequest.value_mask & CWY) ?
736                 e->xconfigurerequest.y : client->area.y;
737             w = (e->xconfigurerequest.value_mask & CWWidth) ?
738                 e->xconfigurerequest.width : client->area.width;
739             h = (e->xconfigurerequest.value_mask & CWHeight) ?
740                 e->xconfigurerequest.height : client->area.height;
741
742             {
743                 int newx = x;
744                 int newy = y;
745                 int fw = w +
746                     client->frame->size.left + client->frame->size.right;
747                 int fh = h +
748                     client->frame->size.top + client->frame->size.bottom;
749                 client_find_onscreen(client, &newx, &newy, fw, fh,
750                                      client_normal(client));
751                 if (e->xconfigurerequest.value_mask & CWX)
752                     x = newx;
753                 if (e->xconfigurerequest.value_mask & CWY)
754                     y = newy;
755             }
756                
757             switch (client->gravity) {
758             case NorthEastGravity:
759             case EastGravity:
760                 corner = OB_CORNER_TOPRIGHT;
761                 break;
762             case SouthWestGravity:
763             case SouthGravity:
764                 corner = OB_CORNER_BOTTOMLEFT;
765                 break;
766             case SouthEastGravity:
767                 corner = OB_CORNER_BOTTOMRIGHT;
768                 break;
769             default:     /* NorthWest, Static, etc */
770                 corner = OB_CORNER_TOPLEFT;
771             }
772
773             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
774                                   TRUE);
775         }
776
777         if (e->xconfigurerequest.value_mask & CWStackMode) {
778             switch (e->xconfigurerequest.detail) {
779             case Below:
780             case BottomIf:
781             client_lower(client);
782             break;
783
784             case Above:
785             case TopIf:
786             default:
787             client_raise(client);
788             break;
789             }
790         }
791         break;
792     case UnmapNotify:
793         if (client->ignore_unmaps) {
794             client->ignore_unmaps--;
795             break;
796         }
797         client_unmanage(client);
798         break;
799     case DestroyNotify:
800         client_unmanage(client);
801         break;
802     case ReparentNotify:
803         /* this is when the client is first taken captive in the frame */
804         if (e->xreparent.parent == client->frame->plate) break;
805
806         /*
807           This event is quite rare and is usually handled in unmapHandler.
808           However, if the window is unmapped when the reparent event occurs,
809           the window manager never sees it because an unmap event is not sent
810           to an already unmapped window.
811         */
812
813         /* we don't want the reparent event, put it back on the stack for the
814            X server to deal with after we unmanage the window */
815         XPutBackEvent(ob_display, e);
816      
817         client_unmanage(client);
818         break;
819     case MapRequest:
820         ob_debug("MapRequest for 0x%lx\n", client->window);
821         if (!client->iconic) break; /* this normally doesn't happen, but if it
822                                        does, we don't want it! */
823         client_activate(client, FALSE);
824         break;
825     case ClientMessage:
826         /* validate cuz we query stuff off the client here */
827         if (!client_validate(client)) break;
828
829         if (e->xclient.format != 32) return;
830
831         msgtype = e->xclient.message_type;
832         if (msgtype == prop_atoms.wm_change_state) {
833             /* compress changes into a single change */
834             while (XCheckTypedWindowEvent(ob_display, client->window,
835                                           e->type, &ce)) {
836                 /* XXX: it would be nice to compress ALL messages of a
837                    type, not just messages in a row without other
838                    message types between. */
839                 if (ce.xclient.message_type != msgtype) {
840                     XPutBackEvent(ob_display, &ce);
841                     break;
842                 }
843                 e->xclient = ce.xclient;
844             }
845             client_set_wm_state(client, e->xclient.data.l[0]);
846         } else if (msgtype == prop_atoms.net_wm_desktop) {
847             /* compress changes into a single change */
848             while (XCheckTypedWindowEvent(ob_display, client->window,
849                                           e->type, &ce)) {
850                 /* XXX: it would be nice to compress ALL messages of a
851                    type, not just messages in a row without other
852                    message types between. */
853                 if (ce.xclient.message_type != msgtype) {
854                     XPutBackEvent(ob_display, &ce);
855                     break;
856                 }
857                 e->xclient = ce.xclient;
858             }
859             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
860                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
861                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
862                                    FALSE);
863         } else if (msgtype == prop_atoms.net_wm_state) {
864             /* can't compress these */
865             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
866                      (e->xclient.data.l[0] == 0 ? "Remove" :
867                       e->xclient.data.l[0] == 1 ? "Add" :
868                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
869                      e->xclient.data.l[1], e->xclient.data.l[2],
870                      client->window);
871             client_set_state(client, e->xclient.data.l[0],
872                              e->xclient.data.l[1], e->xclient.data.l[2]);
873         } else if (msgtype == prop_atoms.net_close_window) {
874             ob_debug("net_close_window for 0x%lx\n", client->window);
875             client_close(client);
876         } else if (msgtype == prop_atoms.net_active_window) {
877             ob_debug("net_active_window for 0x%lx\n", client->window);
878             client_activate(client, FALSE);
879         } else if (msgtype == prop_atoms.net_wm_moveresize) {
880             ob_debug("net_wm_moveresize for 0x%lx\n", client->window);
881             if ((Atom)e->xclient.data.l[2] ==
882                 prop_atoms.net_wm_moveresize_size_topleft ||
883                 (Atom)e->xclient.data.l[2] ==
884                 prop_atoms.net_wm_moveresize_size_top ||
885                 (Atom)e->xclient.data.l[2] ==
886                 prop_atoms.net_wm_moveresize_size_topright ||
887                 (Atom)e->xclient.data.l[2] ==
888                 prop_atoms.net_wm_moveresize_size_right ||
889                 (Atom)e->xclient.data.l[2] ==
890                 prop_atoms.net_wm_moveresize_size_right ||
891                 (Atom)e->xclient.data.l[2] ==
892                 prop_atoms.net_wm_moveresize_size_bottomright ||
893                 (Atom)e->xclient.data.l[2] ==
894                 prop_atoms.net_wm_moveresize_size_bottom ||
895                 (Atom)e->xclient.data.l[2] ==
896                 prop_atoms.net_wm_moveresize_size_bottomleft ||
897                 (Atom)e->xclient.data.l[2] ==
898                 prop_atoms.net_wm_moveresize_size_left ||
899                 (Atom)e->xclient.data.l[2] ==
900                 prop_atoms.net_wm_moveresize_move ||
901                 (Atom)e->xclient.data.l[2] ==
902                 prop_atoms.net_wm_moveresize_size_keyboard ||
903                 (Atom)e->xclient.data.l[2] ==
904                 prop_atoms.net_wm_moveresize_move_keyboard) {
905
906                 moveresize_start(client, e->xclient.data.l[0],
907                                  e->xclient.data.l[1], e->xclient.data.l[3],
908                                  e->xclient.data.l[2]);
909             }
910         } else if (msgtype == prop_atoms.net_moveresize_window) {
911             int oldg = client->gravity;
912             int tmpg, x, y, w, h;
913
914             if (e->xclient.data.l[0] & 0xff)
915                 tmpg = e->xclient.data.l[0] & 0xff;
916             else
917                 tmpg = oldg;
918
919             if (e->xclient.data.l[0] & 1 << 8)
920                 x = e->xclient.data.l[1];
921             else
922                 x = client->area.x;
923             if (e->xclient.data.l[0] & 1 << 9)
924                 y = e->xclient.data.l[2];
925             else
926                 y = client->area.y;
927             if (e->xclient.data.l[0] & 1 << 10)
928                 w = e->xclient.data.l[3];
929             else
930                 w = client->area.width;
931             if (e->xclient.data.l[0] & 1 << 11)
932                 h = e->xclient.data.l[4];
933             else
934                 h = client->area.height;
935             client->gravity = tmpg;
936
937             {
938                 int newx = x;
939                 int newy = y;
940                 int fw = w +
941                     client->frame->size.left + client->frame->size.right;
942                 int fh = h +
943                     client->frame->size.top + client->frame->size.bottom;
944                 client_find_onscreen(client, &newx, &newy, fw, fh,
945                                      client_normal(client));
946                 if (e->xclient.data.l[0] & 1 << 8)
947                     x = newx;
948                 if (e->xclient.data.l[0] & 1 << 9)
949                     y = newy;
950             }
951                
952             client_configure(client, OB_CORNER_TOPLEFT,
953                              x, y, w, h, FALSE, TRUE);
954
955             client->gravity = oldg;
956         }
957         break;
958     case PropertyNotify:
959         /* validate cuz we query stuff off the client here */
960         if (!client_validate(client)) break;
961   
962         /* compress changes to a single property into a single change */
963         while (XCheckTypedWindowEvent(ob_display, client->window,
964                                       e->type, &ce)) {
965             Atom a, b;
966
967             /* XXX: it would be nice to compress ALL changes to a property,
968                not just changes in a row without other props between. */
969
970             a = ce.xproperty.atom;
971             b = e->xproperty.atom;
972
973             if (a == b)
974                 continue;
975             if ((a == prop_atoms.net_wm_name ||
976                  a == prop_atoms.wm_name ||
977                  a == prop_atoms.net_wm_icon_name ||
978                  a == prop_atoms.wm_icon_name)
979                 &&
980                 (b == prop_atoms.net_wm_name ||
981                  b == prop_atoms.wm_name ||
982                  b == prop_atoms.net_wm_icon_name ||
983                  b == prop_atoms.wm_icon_name)) {
984                 continue;
985             }
986             if ((a == prop_atoms.net_wm_icon ||
987                  a == prop_atoms.kwm_win_icon)
988                 &&
989                 (b == prop_atoms.net_wm_icon ||
990                  b == prop_atoms.kwm_win_icon))
991                 continue;
992
993             XPutBackEvent(ob_display, &ce);
994             break;
995         }
996
997         msgtype = e->xproperty.atom;
998         if (msgtype == XA_WM_NORMAL_HINTS) {
999             client_update_normal_hints(client);
1000             /* normal hints can make a window non-resizable */
1001             client_setup_decor_and_functions(client);
1002         } else if (msgtype == XA_WM_HINTS) {
1003             client_update_wmhints(client);
1004         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1005             client_update_transient_for(client);
1006             client_get_type(client);
1007             /* type may have changed, so update the layer */
1008             client_calc_layer(client);
1009             client_setup_decor_and_functions(client);
1010         } else if (msgtype == prop_atoms.net_wm_name ||
1011                    msgtype == prop_atoms.wm_name ||
1012                    msgtype == prop_atoms.net_wm_icon_name ||
1013                    msgtype == prop_atoms.wm_icon_name) {
1014             client_update_title(client);
1015         } else if (msgtype == prop_atoms.wm_class) {
1016             client_update_class(client);
1017         } else if (msgtype == prop_atoms.wm_protocols) {
1018             client_update_protocols(client);
1019             client_setup_decor_and_functions(client);
1020         }
1021         else if (msgtype == prop_atoms.net_wm_strut) {
1022             client_update_strut(client);
1023         }
1024         else if (msgtype == prop_atoms.net_wm_icon ||
1025                  msgtype == prop_atoms.kwm_win_icon) {
1026             client_update_icons(client);
1027         }
1028         else if (msgtype == prop_atoms.sm_client_id) {
1029             client_update_sm_client_id(client);
1030         }
1031     default:
1032         ;
1033 #ifdef SHAPE
1034         if (extensions_shape && e->type == extensions_shape_event_basep) {
1035             client->shaped = ((XShapeEvent*)e)->shaped;
1036             frame_adjust_shape(client->frame);
1037         }
1038 #endif
1039     }
1040 }
1041
1042 static void event_handle_dock(ObDock *s, XEvent *e)
1043 {
1044     switch (e->type) {
1045     case ButtonPress:
1046         stacking_raise(DOCK_AS_WINDOW(s));
1047         break;
1048     case EnterNotify:
1049         dock_hide(FALSE);
1050         break;
1051     case LeaveNotify:
1052         dock_hide(TRUE);
1053         break;
1054     }
1055 }
1056
1057 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1058 {
1059     switch (e->type) {
1060     case MotionNotify:
1061         dock_app_drag(app, &e->xmotion);
1062         break;
1063     case UnmapNotify:
1064         if (app->ignore_unmaps) {
1065             app->ignore_unmaps--;
1066             break;
1067         }
1068         dock_remove(app, TRUE);
1069         break;
1070     case DestroyNotify:
1071         dock_remove(app, FALSE);
1072         break;
1073     case ReparentNotify:
1074         dock_remove(app, FALSE);
1075         break;
1076     case ConfigureNotify:
1077         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1078         break;
1079     }
1080 }
1081
1082 ObMenuFrame* find_active_menu()
1083 {
1084     GList *it;
1085     ObMenuFrame *ret = NULL;
1086
1087     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1088         ret = it->data;
1089         if (ret->selected)
1090             break;
1091         ret = NULL;
1092     }
1093     return ret;
1094 }
1095
1096 ObMenuFrame* find_active_or_last_menu()
1097 {
1098     ObMenuFrame *ret = NULL;
1099
1100     ret = find_active_menu();
1101     if (!ret && menu_frame_visible)
1102         ret = menu_frame_visible->data;
1103     return ret;
1104 }
1105
1106 static void event_handle_menu(XEvent *ev)
1107 {
1108     ObMenuFrame *f;
1109     ObMenuEntryFrame *e;
1110
1111     switch (ev->type) {
1112     case ButtonRelease:
1113         if (menu_can_hide) {
1114             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1115                                             ev->xbutton.y_root)))
1116                 menu_entry_frame_execute(e, ev->xbutton.state);
1117             else
1118                 menu_frame_hide_all();
1119         }
1120         break;
1121     case MotionNotify:
1122         if ((f = menu_frame_under(ev->xmotion.x_root,
1123                                   ev->xmotion.y_root))) {
1124             menu_frame_move_on_screen(f);
1125             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1126                                             ev->xmotion.y_root)))
1127                 menu_frame_select(f, e);
1128         }
1129         {
1130             ObMenuFrame *a;
1131
1132             a = find_active_menu();
1133             if (a && a != f &&
1134                 a->selected->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1135             {
1136                 menu_frame_select(a, NULL);
1137             }
1138         }
1139         break;
1140     case KeyPress:
1141         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1142             menu_frame_hide_all();
1143         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1144             ObMenuFrame *f;
1145             if ((f = find_active_menu()))
1146                 menu_entry_frame_execute(f->selected, ev->xkey.state);
1147         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1148             ObMenuFrame *f;
1149             if ((f = find_active_or_last_menu()) && f->parent)
1150                 menu_frame_select(f, NULL);
1151         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1152             ObMenuFrame *f;
1153             if ((f = find_active_or_last_menu()) && f->child)
1154                 menu_frame_select_next(f->child);
1155         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1156             ObMenuFrame *f;
1157             if ((f = find_active_or_last_menu()))
1158                 menu_frame_select_previous(f);
1159         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1160             ObMenuFrame *f;
1161             if ((f = find_active_or_last_menu()))
1162                 menu_frame_select_next(f);
1163         }
1164         break;
1165     }
1166 }
1167
1168 static gboolean menu_hide_delay_func(gpointer data)
1169 {
1170     menu_can_hide = TRUE;
1171     return FALSE; /* no repeat */
1172 }
1173
1174 static gboolean focus_delay_func(gpointer data)
1175 {
1176     ObClient *c = data;
1177
1178     if (focus_client != c) {
1179         client_focus(c);
1180         if (config_focus_raise)
1181             client_raise(c);
1182     }
1183     return FALSE; /* no repeat */
1184 }
1185
1186 static void focus_delay_client_dest(ObClient *client, gpointer data)
1187 {
1188     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func, client);
1189 }
1190
1191 void event_ignore_queued_enters()
1192 {
1193     GSList *saved = NULL, *it;
1194     XEvent *e;
1195                 
1196     XSync(ob_display, FALSE);
1197
1198     /* count the events */
1199     while (TRUE) {
1200         e = g_new(XEvent, 1);
1201         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1202             saved = g_slist_append(saved, e);
1203             ++ignore_enter_focus;
1204         } else {
1205             g_free(e);
1206             break;
1207         }
1208     }
1209     /* put the events back */
1210     for (it = saved; it; it = g_slist_next(it)) {
1211         XPutBackEvent(ob_display, it->data);
1212         g_free(it->data);
1213     }
1214     g_slist_free(saved);
1215 }