remove mysterious xkb code...
[mikachu/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) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "event.h"
21 #include "debug.h"
22 #include "window.h"
23 #include "openbox.h"
24 #include "dock.h"
25 #include "actions.h"
26 #include "client.h"
27 #include "xerror.h"
28 #include "prop.h"
29 #include "config.h"
30 #include "screen.h"
31 #include "frame.h"
32 #include "grab.h"
33 #include "menu.h"
34 #include "menuframe.h"
35 #include "keyboard.h"
36 #include "modkeys.h"
37 #include "propwin.h"
38 #include "mouse.h"
39 #include "mainloop.h"
40 #include "focus.h"
41 #include "focus_cycle.h"
42 #include "moveresize.h"
43 #include "group.h"
44 #include "stacking.h"
45 #include "extensions.h"
46 #include "translate.h"
47
48 #include <X11/Xlib.h>
49 #include <X11/Xatom.h>
50 #include <glib.h>
51
52 #ifdef HAVE_SYS_SELECT_H
53 #  include <sys/select.h>
54 #endif
55 #ifdef HAVE_SIGNAL_H
56 #  include <signal.h>
57 #endif
58 #ifdef HAVE_UNISTD_H
59 #  include <unistd.h> /* for usleep() */
60 #endif
61 #ifdef XKB
62 #  include <X11/XKBlib.h>
63 #endif
64
65 #ifdef USE_SM
66 #include <X11/ICE/ICElib.h>
67 #endif
68
69 typedef struct
70 {
71     gboolean ignored;
72 } ObEventData;
73
74 typedef struct
75 {
76     ObClient *client;
77     Time time;
78 } ObFocusDelayData;
79
80 typedef struct
81 {
82     gulong start; /* inclusive */
83     gulong end;   /* inclusive */
84 } ObSerialRange;
85
86 static void event_process(const XEvent *e, gpointer data);
87 static void event_handle_root(XEvent *e);
88 static gboolean event_handle_menu_keyboard(XEvent *e);
89 static gboolean event_handle_menu(XEvent *e);
90 static void event_handle_dock(ObDock *s, XEvent *e);
91 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
92 static void event_handle_client(ObClient *c, XEvent *e);
93 static void event_handle_user_time_window_clients(GSList *l, XEvent *e);
94 static void event_handle_user_input(ObClient *client, XEvent *e);
95 static gboolean is_enter_focus_event_ignored(XEvent *e);
96
97 static void focus_delay_dest(gpointer data);
98 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
99 static gboolean focus_delay_func(gpointer data);
100 static void focus_delay_client_dest(ObClient *client, gpointer data);
101
102 /* The time for the current event being processed */
103 Time event_curtime = CurrentTime;
104
105 static gboolean focus_left_screen = FALSE;
106 /*! A list of ObSerialRanges which are to be ignored for mouse enter events */
107 static GSList *ignore_serials = NULL;
108
109 #ifdef USE_SM
110 static void ice_handler(gint fd, gpointer conn)
111 {
112     Bool b;
113     IceProcessMessages(conn, NULL, &b);
114 }
115
116 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
117                       IcePointer *watch_data)
118 {
119     static gint fd = -1;
120
121     if (opening) {
122         fd = IceConnectionNumber(conn);
123         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
124     } else {
125         ob_main_loop_fd_remove(ob_main_loop, fd);
126         fd = -1;
127     }
128 }
129 #endif
130
131 void event_startup(gboolean reconfig)
132 {
133     if (reconfig) return;
134
135     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
136
137 #ifdef USE_SM
138     IceAddConnectionWatch(ice_watch, NULL);
139 #endif
140
141     client_add_destroy_notify(focus_delay_client_dest, NULL);
142 }
143
144 void event_shutdown(gboolean reconfig)
145 {
146     if (reconfig) return;
147
148 #ifdef USE_SM
149     IceRemoveConnectionWatch(ice_watch, NULL);
150 #endif
151
152     client_remove_destroy_notify(focus_delay_client_dest);
153 }
154
155 static Window event_get_window(XEvent *e)
156 {
157     Window window;
158
159     /* pick a window */
160     switch (e->type) {
161     case SelectionClear:
162         window = RootWindow(ob_display, ob_screen);
163         break;
164     case MapRequest:
165         window = e->xmap.window;
166         break;
167     case UnmapNotify:
168         window = e->xunmap.window;
169         break;
170     case DestroyNotify:
171         window = e->xdestroywindow.window;
172         break;
173     case ConfigureRequest:
174         window = e->xconfigurerequest.window;
175         break;
176     case ConfigureNotify:
177         window = e->xconfigure.window;
178         break;
179     default:
180 #ifdef XKB
181         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
182             switch (((XkbAnyEvent*)e)->xkb_type) {
183             case XkbBellNotify:
184                 window = ((XkbBellNotifyEvent*)e)->window;
185             default:
186                 window = None;
187             }
188         } else
189 #endif
190 #ifdef SYNC
191         if (extensions_sync &&
192             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
193         {
194             window = None;
195         } else
196 #endif
197             window = e->xany.window;
198     }
199     return window;
200 }
201
202 static void event_set_curtime(XEvent *e)
203 {
204     Time t = CurrentTime;
205
206     /* grab the lasttime and hack up the state */
207     switch (e->type) {
208     case ButtonPress:
209     case ButtonRelease:
210         t = e->xbutton.time;
211         break;
212     case KeyPress:
213         t = e->xkey.time;
214         break;
215     case KeyRelease:
216         t = e->xkey.time;
217         break;
218     case MotionNotify:
219         t = e->xmotion.time;
220         break;
221     case PropertyNotify:
222         t = e->xproperty.time;
223         break;
224     case EnterNotify:
225     case LeaveNotify:
226         t = e->xcrossing.time;
227         break;
228     default:
229 #ifdef SYNC
230         if (extensions_sync &&
231             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
232         {
233             t = ((XSyncAlarmNotifyEvent*)e)->time;
234         }
235 #endif
236         /* if more event types are anticipated, get their timestamp
237            explicitly */
238         break;
239     }
240
241     event_curtime = t;
242 }
243
244 static void event_hack_mods(XEvent *e)
245 {
246 #ifdef XKB
247     XkbStateRec xkb_state;
248 #endif
249
250     switch (e->type) {
251     case ButtonPress:
252     case ButtonRelease:
253         e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
254         break;
255     case KeyPress:
256         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
257         break;
258     case KeyRelease:
259         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
260         /* remove from the state the mask of the modifier key being released,
261            if it is a modifier key being released that is */
262         e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
263         break;
264     case MotionNotify:
265         e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
266         /* compress events */
267         {
268             XEvent ce;
269             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
270                                           e->type, &ce)) {
271                 e->xmotion.x = ce.xmotion.x;
272                 e->xmotion.y = ce.xmotion.y;
273                 e->xmotion.x_root = ce.xmotion.x_root;
274                 e->xmotion.y_root = ce.xmotion.y_root;
275             }
276         }
277         break;
278     }
279 }
280
281 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
282 {
283     gint mode = e->xfocus.mode;
284     gint detail = e->xfocus.detail;
285     Window win = e->xany.window;
286
287     if (e->type == FocusIn) {
288         /* These are ones we never want.. */
289
290         /* This means focus was given by a keyboard/mouse grab. */
291         if (mode == NotifyGrab)
292             return FALSE;
293         /* This means focus was given back from a keyboard/mouse grab. */
294         if (mode == NotifyUngrab)
295             return FALSE;
296
297         /* These are the ones we want.. */
298
299         if (win == RootWindow(ob_display, ob_screen)) {
300             /* If looking for a focus in on a client, then always return
301                FALSE for focus in's to the root window */
302             if (in_client_only)
303                 return FALSE;
304             /* This means focus reverted off of a client */
305             else if (detail == NotifyPointerRoot ||
306                      detail == NotifyDetailNone ||
307                      detail == NotifyInferior ||
308                      /* This means focus got here from another screen */
309                      detail == NotifyNonlinear)
310                 return TRUE;
311             else
312                 return FALSE;
313         }
314
315         /* It was on a client, was it a valid one?
316            It's possible to get a FocusIn event for a client that was managed
317            but has disappeared.
318         */
319         if (in_client_only) {
320             ObWindow *w = g_hash_table_lookup(window_map, &e->xfocus.window);
321             if (!w || !WINDOW_IS_CLIENT(w))
322                 return FALSE;
323         }
324         else {
325             /* This means focus reverted to parent from the client (this
326                happens often during iconify animation) */
327             if (detail == NotifyInferior)
328                 return TRUE;
329         }
330
331         /* This means focus moved from the root window to a client */
332         if (detail == NotifyVirtual)
333             return TRUE;
334         /* This means focus moved from one client to another */
335         if (detail == NotifyNonlinearVirtual)
336             return TRUE;
337
338         /* Otherwise.. */
339         return FALSE;
340     } else {
341         g_assert(e->type == FocusOut);
342
343         /* These are ones we never want.. */
344
345         /* This means focus was taken by a keyboard/mouse grab. */
346         if (mode == NotifyGrab)
347             return FALSE;
348         /* This means focus was grabbed on a window and it was released. */
349         if (mode == NotifyUngrab)
350             return FALSE;
351
352         /* Focus left the root window revertedto state */
353         if (win == RootWindow(ob_display, ob_screen))
354             return FALSE;
355
356         /* These are the ones we want.. */
357
358         /* This means focus moved from a client to the root window */
359         if (detail == NotifyVirtual)
360             return TRUE;
361         /* This means focus moved from one client to another */
362         if (detail == NotifyNonlinearVirtual)
363             return TRUE;
364
365         /* Otherwise.. */
366         return FALSE;
367     }
368 }
369
370 static Bool event_look_for_focusin(Display *d, XEvent *e, XPointer arg)
371 {
372     return e->type == FocusIn && wanted_focusevent(e, FALSE);
373 }
374
375 static Bool event_look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
376 {
377     return e->type == FocusIn && wanted_focusevent(e, TRUE);
378 }
379
380 static void print_focusevent(XEvent *e)
381 {
382     gint mode = e->xfocus.mode;
383     gint detail = e->xfocus.detail;
384     Window win = e->xany.window;
385     const gchar *modestr, *detailstr;
386
387     switch (mode) {
388     case NotifyNormal:       modestr="NotifyNormal";       break;
389     case NotifyGrab:         modestr="NotifyGrab";         break;
390     case NotifyUngrab:       modestr="NotifyUngrab";       break;
391     case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break;
392     }
393     switch (detail) {
394     case NotifyAncestor:    detailstr="NotifyAncestor";    break;
395     case NotifyVirtual:     detailstr="NotifyVirtual";     break;
396     case NotifyInferior:    detailstr="NotifyInferior";    break;
397     case NotifyNonlinear:   detailstr="NotifyNonlinear";   break;
398     case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
399     case NotifyPointer:     detailstr="NotifyPointer";     break;
400     case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
401     case NotifyDetailNone:  detailstr="NotifyDetailNone";  break;
402     }
403
404     if (mode == NotifyGrab || mode == NotifyUngrab)
405         return;
406
407     g_assert(modestr);
408     g_assert(detailstr);
409     ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s\n",
410                   (e->xfocus.type == FocusIn ? "In" : "Out"),
411                   win,
412                   modestr, detailstr);
413
414 }
415
416 static gboolean event_ignore(XEvent *e, ObClient *client)
417 {
418     switch(e->type) {
419     case FocusIn:
420         print_focusevent(e);
421         if (!wanted_focusevent(e, FALSE))
422             return TRUE;
423         break;
424     case FocusOut:
425         print_focusevent(e);
426         if (!wanted_focusevent(e, FALSE))
427             return TRUE;
428         break;
429     }
430     return FALSE;
431 }
432
433 static void event_process(const XEvent *ec, gpointer data)
434 {
435     Window window;
436     ObClient *client = NULL;
437     ObDock *dock = NULL;
438     ObDockApp *dockapp = NULL;
439     ObWindow *obwin = NULL;
440     GSList *timewinclients = NULL;
441     XEvent ee, *e;
442     ObEventData *ed = data;
443
444     /* make a copy we can mangle */
445     ee = *ec;
446     e = &ee;
447
448     window = event_get_window(e);
449     if (e->type != PropertyNotify ||
450         !(timewinclients = propwin_get_clients(window,
451                                                OB_PROPWIN_USER_TIME)))
452         if ((obwin = g_hash_table_lookup(window_map, &window))) {
453             switch (obwin->type) {
454             case Window_Dock:
455                 dock = WINDOW_AS_DOCK(obwin);
456                 break;
457             case Window_DockApp:
458                 dockapp = WINDOW_AS_DOCKAPP(obwin);
459                 break;
460             case Window_Client:
461                 client = WINDOW_AS_CLIENT(obwin);
462                 break;
463             case Window_Menu:
464             case Window_Internal:
465                 /* not to be used for events */
466                 g_assert_not_reached();
467                 break;
468             }
469         }
470
471     event_set_curtime(e);
472     event_hack_mods(e);
473     if (event_ignore(e, client)) {
474         if (ed)
475             ed->ignored = TRUE;
476         return;
477     } else if (ed)
478             ed->ignored = FALSE;
479
480     /* deal with it in the kernel */
481
482     if (menu_frame_visible &&
483         (e->type == EnterNotify || e->type == LeaveNotify))
484     {
485         /* crossing events for menu */
486         event_handle_menu(e);
487     } else if (e->type == FocusIn) {
488         if (client &&
489             e->xfocus.detail == NotifyInferior)
490         {
491             ob_debug_type(OB_DEBUG_FOCUS,
492                           "Focus went to the frame window");
493
494             focus_left_screen = FALSE;
495
496             focus_fallback(FALSE, config_focus_under_mouse, TRUE);
497
498             /* We don't get a FocusOut for this case, because it's just moving
499                from our Inferior up to us. This happens when iconifying a
500                window with RevertToParent focus */
501             frame_adjust_focus(client->frame, FALSE);
502             /* focus_set_client(NULL) has already been called */
503             client_calc_layer(client);
504         }
505         if (e->xfocus.detail == NotifyPointerRoot ||
506             e->xfocus.detail == NotifyDetailNone ||
507             e->xfocus.detail == NotifyInferior ||
508             e->xfocus.detail == NotifyNonlinear)
509         {
510             XEvent ce;
511
512             ob_debug_type(OB_DEBUG_FOCUS,
513                           "Focus went to root or pointer root/none\n");
514
515             if (e->xfocus.detail == NotifyInferior ||
516                 e->xfocus.detail == NotifyNonlinear)
517             {
518                 focus_left_screen = FALSE;
519             }
520
521             /* If another FocusIn is in the queue then don't fallback yet. This
522                fixes the fun case of:
523                window map -> send focusin
524                window unmap -> get focusout
525                window map -> send focusin
526                get first focus out -> fall back to something (new window
527                  hasn't received focus yet, so something else) -> send focusin
528                which means the "something else" is the last thing to get a
529                focusin sent to it, so the new window doesn't end up with focus.
530
531                But if the other focus in is something like PointerRoot then we
532                still want to fall back.
533             */
534             if (XCheckIfEvent(ob_display, &ce, event_look_for_focusin_client,
535                               NULL))
536             {
537                 XPutBackEvent(ob_display, &ce);
538                 ob_debug_type(OB_DEBUG_FOCUS,
539                               "  but another FocusIn is coming\n");
540             } else {
541                 /* Focus has been reverted.
542
543                    FocusOut events come after UnmapNotify, so we don't need to
544                    worry about focusing an invalid window
545                 */
546
547                 if (!focus_left_screen)
548                     focus_fallback(FALSE, config_focus_under_mouse, TRUE);
549             }
550         }
551         else if (!client)
552         {
553             ob_debug_type(OB_DEBUG_FOCUS,
554                           "Focus went to a window that is already gone\n");
555
556             /* If you send focus to a window and then it disappears, you can
557                get the FocusIn for it, after it is unmanaged.
558                Just wait for the next FocusOut/FocusIn pair, but make note that
559                the window that was focused no longer is. */
560             focus_set_client(NULL);
561         }
562         else if (client != focus_client) {
563             focus_left_screen = FALSE;
564             frame_adjust_focus(client->frame, TRUE);
565             focus_set_client(client);
566             client_calc_layer(client);
567             client_bring_helper_windows(client);
568         }
569     } else if (e->type == FocusOut) {
570         XEvent ce;
571
572         /* Look for the followup FocusIn */
573         if (!XCheckIfEvent(ob_display, &ce, event_look_for_focusin, NULL)) {
574             /* There is no FocusIn, this means focus went to a window that
575                is not being managed, or a window on another screen. */
576             Window win, root;
577             gint i;
578             guint u;
579             xerror_set_ignore(TRUE);
580             if (XGetInputFocus(ob_display, &win, &i) != 0 &&
581                 XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 &&
582                 root != RootWindow(ob_display, ob_screen))
583             {
584                 ob_debug_type(OB_DEBUG_FOCUS,
585                               "Focus went to another screen !\n");
586                 focus_left_screen = TRUE;
587             }
588             else
589                 ob_debug_type(OB_DEBUG_FOCUS,
590                               "Focus went to a black hole !\n");
591             xerror_set_ignore(FALSE);
592             /* nothing is focused */
593             focus_set_client(NULL);
594         } else {
595             /* Focus moved, so process the FocusIn event */
596             ObEventData ed = { .ignored = FALSE };
597             event_process(&ce, &ed);
598             if (ed.ignored) {
599                 /* The FocusIn was ignored, this means it was on a window
600                    that isn't a client. */
601                 ob_debug_type(OB_DEBUG_FOCUS,
602                               "Focus went to an unmanaged window 0x%x !\n",
603                               ce.xfocus.window);
604                 focus_fallback(TRUE, config_focus_under_mouse, TRUE);
605             }
606         }
607
608         if (client && client != focus_client) {
609             frame_adjust_focus(client->frame, FALSE);
610             /* focus_set_client(NULL) has already been called in this
611                section or by focus_fallback */
612             client_calc_layer(client);
613         }
614     } else if (timewinclients)
615         event_handle_user_time_window_clients(timewinclients, e);
616     else if (client)
617         event_handle_client(client, e);
618     else if (dockapp)
619         event_handle_dockapp(dockapp, e);
620     else if (dock)
621         event_handle_dock(dock, e);
622     else if (window == RootWindow(ob_display, ob_screen))
623         event_handle_root(e);
624     else if (e->type == MapRequest)
625         client_manage(window);
626     else if (e->type == ClientMessage) {
627         /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
628            windows that are not managed yet. */
629         if (e->xclient.message_type == prop_atoms.net_request_frame_extents) {
630             /* Pretend to manage the client, getting information used to
631                determine its decorations */
632             ObClient *c = client_fake_manage(e->xclient.window);
633             gulong vals[4];
634
635             /* set the frame extents on the window */
636             vals[0] = c->frame->size.left;
637             vals[1] = c->frame->size.right;
638             vals[2] = c->frame->size.top;
639             vals[3] = c->frame->size.bottom;
640             PROP_SETA32(e->xclient.window, net_frame_extents,
641                         cardinal, vals, 4);
642
643             /* Free the pretend client */
644             client_fake_unmanage(c);
645         }
646     }
647     else if (e->type == ConfigureRequest) {
648         /* unhandled configure requests must be used to configure the
649            window directly */
650         XWindowChanges xwc;
651
652         xwc.x = e->xconfigurerequest.x;
653         xwc.y = e->xconfigurerequest.y;
654         xwc.width = e->xconfigurerequest.width;
655         xwc.height = e->xconfigurerequest.height;
656         xwc.border_width = e->xconfigurerequest.border_width;
657         xwc.sibling = e->xconfigurerequest.above;
658         xwc.stack_mode = e->xconfigurerequest.detail;
659
660         /* we are not to be held responsible if someone sends us an
661            invalid request! */
662         xerror_set_ignore(TRUE);
663         XConfigureWindow(ob_display, window,
664                          e->xconfigurerequest.value_mask, &xwc);
665         xerror_set_ignore(FALSE);
666     }
667 #ifdef SYNC
668     else if (extensions_sync &&
669         e->type == extensions_sync_event_basep + XSyncAlarmNotify)
670     {
671         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
672         if (se->alarm == moveresize_alarm && moveresize_in_progress)
673             moveresize_event(e);
674     }
675 #endif
676
677     if (e->type == ButtonPress || e->type == ButtonRelease ||
678         e->type == MotionNotify || e->type == KeyPress ||
679         e->type == KeyRelease)
680     {
681         event_handle_user_input(client, e);
682     }
683
684     /* if something happens and it's not from an XEvent, then we don't know
685        the time */
686     event_curtime = CurrentTime;
687 }
688
689 static void event_handle_root(XEvent *e)
690 {
691     Atom msgtype;
692
693     switch(e->type) {
694     case SelectionClear:
695         ob_debug("Another WM has requested to replace us. Exiting.\n");
696         ob_exit_replace();
697         break;
698
699     case ClientMessage:
700         if (e->xclient.format != 32) break;
701
702         msgtype = e->xclient.message_type;
703         if (msgtype == prop_atoms.net_current_desktop) {
704             guint d = e->xclient.data.l[0];
705             if (d < screen_num_desktops) {
706                 event_curtime = e->xclient.data.l[1];
707                 if (event_curtime == 0)
708                     ob_debug_type(OB_DEBUG_APP_BUGS,
709                                   "_NET_CURRENT_DESKTOP message is missing "
710                                   "a timestamp\n");
711                 screen_set_desktop(d, TRUE);
712             }
713         } else if (msgtype == prop_atoms.net_number_of_desktops) {
714             guint d = e->xclient.data.l[0];
715             if (d > 0 && d <= 1000)
716                 screen_set_num_desktops(d);
717         } else if (msgtype == prop_atoms.net_showing_desktop) {
718             screen_show_desktop(e->xclient.data.l[0] != 0, NULL);
719         } else if (msgtype == prop_atoms.ob_control) {
720             ob_debug("OB_CONTROL: %d\n", e->xclient.data.l[0]);
721             if (e->xclient.data.l[0] == 1)
722                 ob_reconfigure();
723             else if (e->xclient.data.l[0] == 2)
724                 ob_restart();
725         }
726         break;
727     case PropertyNotify:
728         if (e->xproperty.atom == prop_atoms.net_desktop_names) {
729             ob_debug("UPDATE DESKTOP NAMES\n");
730             screen_update_desktop_names();
731         }
732         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
733             screen_update_layout();
734         break;
735     case ConfigureNotify:
736 #ifdef XRANDR
737         XRRUpdateConfiguration(e);
738 #endif
739         screen_resize();
740         break;
741     default:
742         ;
743     }
744 }
745
746 void event_enter_client(ObClient *client)
747 {
748     g_assert(config_focus_follow);
749
750     if (client_enter_focusable(client) && client_can_focus(client)) {
751         if (config_focus_delay) {
752             ObFocusDelayData *data;
753
754             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
755
756             data = g_new(ObFocusDelayData, 1);
757             data->client = client;
758             data->time = event_curtime;
759
760             ob_main_loop_timeout_add(ob_main_loop,
761                                      config_focus_delay,
762                                      focus_delay_func,
763                                      data, focus_delay_cmp, focus_delay_dest);
764         } else {
765             ObFocusDelayData data;
766             data.client = client;
767             data.time = event_curtime;
768             focus_delay_func(&data);
769         }
770     }
771 }
772
773 static void event_handle_user_time_window_clients(GSList *l, XEvent *e)
774 {
775     g_assert(e->type == PropertyNotify);
776     if (e->xproperty.atom == prop_atoms.net_wm_user_time) {
777         for (; l; l = g_slist_next(l))
778             client_update_user_time(l->data);
779     }
780 }
781
782 static void event_handle_client(ObClient *client, XEvent *e)
783 {
784     XEvent ce;
785     Atom msgtype;
786     ObFrameContext con;
787     static gint px = -1, py = -1;
788     static guint pb = 0;
789
790     switch (e->type) {
791     case ButtonPress:
792         /* save where the press occured for the first button pressed */
793         if (!pb) {
794             pb = e->xbutton.button;
795             px = e->xbutton.x;
796             py = e->xbutton.y;
797         }
798     case ButtonRelease:
799         /* Wheel buttons don't draw because they are an instant click, so it
800            is a waste of resources to go drawing it.
801            if the user is doing an intereactive thing, or has a menu open then
802            the mouse is grabbed (possibly) and if we get these events we don't
803            want to deal with them
804         */
805         if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
806             !grab_on_keyboard())
807         {
808             /* use where the press occured */
809             con = frame_context(client, e->xbutton.window, px, py);
810             con = mouse_button_frame_context(con, e->xbutton.button,
811                                              e->xbutton.state);
812
813             if (e->type == ButtonRelease && e->xbutton.button == pb)
814                 pb = 0, px = py = -1;
815
816             switch (con) {
817             case OB_FRAME_CONTEXT_MAXIMIZE:
818                 client->frame->max_press = (e->type == ButtonPress);
819                 frame_adjust_state(client->frame);
820                 break;
821             case OB_FRAME_CONTEXT_CLOSE:
822                 client->frame->close_press = (e->type == ButtonPress);
823                 frame_adjust_state(client->frame);
824                 break;
825             case OB_FRAME_CONTEXT_ICONIFY:
826                 client->frame->iconify_press = (e->type == ButtonPress);
827                 frame_adjust_state(client->frame);
828                 break;
829             case OB_FRAME_CONTEXT_ALLDESKTOPS:
830                 client->frame->desk_press = (e->type == ButtonPress);
831                 frame_adjust_state(client->frame);
832                 break;
833             case OB_FRAME_CONTEXT_SHADE:
834                 client->frame->shade_press = (e->type == ButtonPress);
835                 frame_adjust_state(client->frame);
836                 break;
837             default:
838                 /* nothing changes with clicks for any other contexts */
839                 break;
840             }
841         }
842         break;
843     case MotionNotify:
844         /* when there is a grab on the pointer, we won't get enter/leave
845            notifies, but we still get motion events */
846         if (grab_on_pointer()) break;
847
848         con = frame_context(client, e->xmotion.window,
849                             e->xmotion.x, e->xmotion.y);
850         switch (con) {
851         case OB_FRAME_CONTEXT_TITLEBAR:
852         case OB_FRAME_CONTEXT_TLCORNER:
853         case OB_FRAME_CONTEXT_TRCORNER:
854             /* we've left the button area inside the titlebar */
855             if (client->frame->max_hover || client->frame->desk_hover ||
856                 client->frame->shade_hover || client->frame->iconify_hover ||
857                 client->frame->close_hover)
858             {
859                 client->frame->max_hover = FALSE;
860                 client->frame->desk_hover = FALSE;
861                 client->frame->shade_hover = FALSE;
862                 client->frame->iconify_hover = FALSE;
863                 client->frame->close_hover = FALSE;
864                 frame_adjust_state(client->frame);
865             }
866             break;
867         case OB_FRAME_CONTEXT_MAXIMIZE:
868             if (!client->frame->max_hover) {
869                 client->frame->max_hover = TRUE;
870                 frame_adjust_state(client->frame);
871             }
872             break;
873         case OB_FRAME_CONTEXT_ALLDESKTOPS:
874             if (!client->frame->desk_hover) {
875                 client->frame->desk_hover = TRUE;
876                 frame_adjust_state(client->frame);
877             }
878             break;
879         case OB_FRAME_CONTEXT_SHADE:
880             if (!client->frame->shade_hover) {
881                 client->frame->shade_hover = TRUE;
882                 frame_adjust_state(client->frame);
883             }
884             break;
885         case OB_FRAME_CONTEXT_ICONIFY:
886             if (!client->frame->iconify_hover) {
887                 client->frame->iconify_hover = TRUE;
888                 frame_adjust_state(client->frame);
889             }
890             break;
891         case OB_FRAME_CONTEXT_CLOSE:
892             if (!client->frame->close_hover) {
893                 client->frame->close_hover = TRUE;
894                 frame_adjust_state(client->frame);
895             }
896             break;
897         default:
898             break;
899         }
900         break;
901     case LeaveNotify:
902         con = frame_context(client, e->xcrossing.window,
903                             e->xcrossing.x, e->xcrossing.y);
904         switch (con) {
905         case OB_FRAME_CONTEXT_TITLEBAR:
906         case OB_FRAME_CONTEXT_TLCORNER:
907         case OB_FRAME_CONTEXT_TRCORNER:
908             /* we've left the button area inside the titlebar */
909             if (client->frame->max_hover || client->frame->desk_hover ||
910                 client->frame->shade_hover || client->frame->iconify_hover ||
911                 client->frame->close_hover)
912             {
913                 client->frame->max_hover = FALSE;
914                 client->frame->desk_hover = FALSE;
915                 client->frame->shade_hover = FALSE;
916                 client->frame->iconify_hover = FALSE;
917                 client->frame->close_hover = FALSE;
918                 frame_adjust_state(client->frame);
919             }
920             break;
921         case OB_FRAME_CONTEXT_MAXIMIZE:
922             client->frame->max_hover = FALSE;
923             frame_adjust_state(client->frame);
924             break;
925         case OB_FRAME_CONTEXT_ALLDESKTOPS:
926             client->frame->desk_hover = FALSE;
927             frame_adjust_state(client->frame);
928             break;
929         case OB_FRAME_CONTEXT_SHADE:
930             client->frame->shade_hover = FALSE;
931             frame_adjust_state(client->frame);
932             break;
933         case OB_FRAME_CONTEXT_ICONIFY:
934             client->frame->iconify_hover = FALSE;
935             frame_adjust_state(client->frame);
936             break;
937         case OB_FRAME_CONTEXT_CLOSE:
938             client->frame->close_hover = FALSE;
939             frame_adjust_state(client->frame);
940             break;
941         case OB_FRAME_CONTEXT_FRAME:
942             /* When the mouse leaves an animating window, don't use the
943                corresponding enter events. Pretend like the animating window
944                doesn't even exist..! */
945             if (frame_iconify_animating(client->frame))
946                 event_end_ignore_all_enters(event_start_ignore_all_enters());
947
948             ob_debug_type(OB_DEBUG_FOCUS,
949                           "%sNotify mode %d detail %d on %lx\n",
950                           (e->type == EnterNotify ? "Enter" : "Leave"),
951                           e->xcrossing.mode,
952                           e->xcrossing.detail, (client?client->window:0));
953             if (grab_on_keyboard())
954                 break;
955             if (config_focus_follow && config_focus_delay &&
956                 /* leave inferior events can happen when the mouse goes onto
957                    the window's border and then into the window before the
958                    delay is up */
959                 e->xcrossing.detail != NotifyInferior)
960             {
961                 ob_main_loop_timeout_remove_data(ob_main_loop,
962                                                  focus_delay_func,
963                                                  client, FALSE);
964             }
965             break;
966         default:
967             break;
968         }
969         break;
970     case EnterNotify:
971     {
972         con = frame_context(client, e->xcrossing.window,
973                             e->xcrossing.x, e->xcrossing.y);
974         switch (con) {
975         case OB_FRAME_CONTEXT_MAXIMIZE:
976             client->frame->max_hover = TRUE;
977             frame_adjust_state(client->frame);
978             break;
979         case OB_FRAME_CONTEXT_ALLDESKTOPS:
980             client->frame->desk_hover = TRUE;
981             frame_adjust_state(client->frame);
982             break;
983         case OB_FRAME_CONTEXT_SHADE:
984             client->frame->shade_hover = TRUE;
985             frame_adjust_state(client->frame);
986             break;
987         case OB_FRAME_CONTEXT_ICONIFY:
988             client->frame->iconify_hover = TRUE;
989             frame_adjust_state(client->frame);
990             break;
991         case OB_FRAME_CONTEXT_CLOSE:
992             client->frame->close_hover = TRUE;
993             frame_adjust_state(client->frame);
994             break;
995         case OB_FRAME_CONTEXT_FRAME:
996             if (grab_on_keyboard())
997                 break;
998             if (e->xcrossing.mode == NotifyGrab ||
999                 e->xcrossing.mode == NotifyUngrab ||
1000                 /*ignore enters when we're already in the window */
1001                 e->xcrossing.detail == NotifyInferior ||
1002                 is_enter_focus_event_ignored(e))
1003             {
1004                 ob_debug_type(OB_DEBUG_FOCUS,
1005                               "%sNotify mode %d detail %d on %lx IGNORED\n",
1006                               (e->type == EnterNotify ? "Enter" : "Leave"),
1007                               e->xcrossing.mode,
1008                               e->xcrossing.detail, client?client->window:0);
1009             }
1010             else {
1011                 ob_debug_type(OB_DEBUG_FOCUS,
1012                               "%sNotify mode %d detail %d on %lx, "
1013                               "focusing window\n",
1014                               (e->type == EnterNotify ? "Enter" : "Leave"),
1015                               e->xcrossing.mode,
1016                               e->xcrossing.detail, (client?client->window:0));
1017                 if (config_focus_follow)
1018                     event_enter_client(client);
1019             }
1020             break;
1021         default:
1022             break;
1023         }
1024         break;
1025     }
1026     case ConfigureRequest:
1027     {
1028         /* dont compress these unless you're going to watch for property
1029            notifies in between (these can change what the configure would
1030            do to the window).
1031            also you can't compress stacking events
1032         */
1033
1034         gint x, y, w, h;
1035         gboolean move = FALSE;
1036         gboolean resize = FALSE;
1037
1038         /* get the current area */
1039         RECT_TO_DIMS(client->area, x, y, w, h);
1040
1041         ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
1042                  "visibile %d\n"
1043                  "                     x %d y %d w %d h %d b %d\n",
1044                  client->title,
1045                  screen_desktop, client->wmstate, client->frame->visible,
1046                  x, y, w, h, client->border_width);
1047
1048         if (e->xconfigurerequest.value_mask & CWBorderWidth)
1049             if (client->border_width != e->xconfigurerequest.border_width) {
1050                 client->border_width = e->xconfigurerequest.border_width;
1051
1052                 /* if the border width is changing then that is the same
1053                    as requesting a resize, but we don't actually change
1054                    the client's border, so it will change their root
1055                    coordiantes (since they include the border width) and
1056                    we need to a notify then */
1057                 move = TRUE;
1058             }
1059
1060
1061         if (e->xconfigurerequest.value_mask & CWStackMode) {
1062             ObClient *sibling = NULL;
1063             gulong ignore_start;
1064             gboolean ok = TRUE;
1065
1066             /* get the sibling */
1067             if (e->xconfigurerequest.value_mask & CWSibling) {
1068                 ObWindow *win;
1069                 win = g_hash_table_lookup(window_map,
1070                                           &e->xconfigurerequest.above);
1071                 if (win && WINDOW_IS_CLIENT(win) &&
1072                     WINDOW_AS_CLIENT(win) != client)
1073                 {
1074                     sibling = WINDOW_AS_CLIENT(win);
1075                 }
1076                 else
1077                     /* an invalid sibling was specified so don't restack at
1078                        all, it won't make sense no matter what we do */
1079                     ok = FALSE;
1080             }
1081
1082             if (ok) {
1083                 if (!config_focus_under_mouse)
1084                     ignore_start = event_start_ignore_all_enters();
1085                 stacking_restack_request(client, sibling,
1086                                          e->xconfigurerequest.detail);
1087                 if (!config_focus_under_mouse)
1088                     event_end_ignore_all_enters(ignore_start);
1089             }
1090
1091             /* a stacking change moves the window without resizing */
1092             move = TRUE;
1093         }
1094
1095         if ((e->xconfigurerequest.value_mask & CWX) ||
1096             (e->xconfigurerequest.value_mask & CWY) ||
1097             (e->xconfigurerequest.value_mask & CWWidth) ||
1098             (e->xconfigurerequest.value_mask & CWHeight))
1099         {
1100             if (e->xconfigurerequest.value_mask & CWX) {
1101                 /* don't allow clients to move shaded windows (fvwm does this)
1102                  */
1103                 if (!client->shaded)
1104                     x = e->xconfigurerequest.x;
1105                 move = TRUE;
1106             }
1107             if (e->xconfigurerequest.value_mask & CWY) {
1108                 /* don't allow clients to move shaded windows (fvwm does this)
1109                  */
1110                 if (!client->shaded)
1111                     y = e->xconfigurerequest.y;
1112                 move = TRUE;
1113             }
1114
1115             if (e->xconfigurerequest.value_mask & CWWidth) {
1116                 w = e->xconfigurerequest.width;
1117                 resize = TRUE;
1118             }
1119             if (e->xconfigurerequest.value_mask & CWHeight) {
1120                 h = e->xconfigurerequest.height;
1121                 resize = TRUE;
1122             }
1123         }
1124
1125         ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
1126                  "move %d resize %d\n",
1127                  e->xconfigurerequest.value_mask & CWX, x,
1128                  e->xconfigurerequest.value_mask & CWY, y,
1129                  e->xconfigurerequest.value_mask & CWWidth, w,
1130                  e->xconfigurerequest.value_mask & CWHeight, h,
1131                  move, resize);
1132
1133         /* check for broken apps moving to their root position
1134
1135            XXX remove this some day...that would be nice. right now all
1136            kde apps do this when they try activate themselves on another
1137            desktop. eg. open amarok window on desktop 1, switch to desktop
1138            2, click amarok tray icon. it will move by its decoration size.
1139         */
1140         if (x != client->area.x &&
1141             x == (client->frame->area.x + client->frame->size.left -
1142                   (gint)client->border_width) &&
1143             y != client->area.y &&
1144             y == (client->frame->area.y + client->frame->size.top -
1145                   (gint)client->border_width) &&
1146             w == client->area.width &&
1147             h == client->area.height)
1148         {
1149             ob_debug_type(OB_DEBUG_APP_BUGS,
1150                           "Application %s is trying to move via "
1151                           "ConfigureRequest to it's root window position "
1152                           "but it is not using StaticGravity\n",
1153                           client->title);
1154             /* don't move it */
1155             x = client->area.x;
1156             y = client->area.y;
1157
1158             /* they still requested a move, so don't change whether a
1159                notify is sent or not */
1160         }
1161
1162         {
1163             gint lw,lh;
1164
1165             client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1166
1167             /* if x was not given, then use gravity to figure out the new
1168                x.  the reference point should not be moved */
1169             if ((e->xconfigurerequest.value_mask & CWWidth &&
1170                  !(e->xconfigurerequest.value_mask & CWX)))
1171                 client_gravity_resize_w(client, &x, client->area.width, w);
1172             /* if y was not given, then use gravity to figure out the new
1173                y.  the reference point should not be moved */
1174             if ((e->xconfigurerequest.value_mask & CWHeight &&
1175                  !(e->xconfigurerequest.value_mask & CWY)))
1176                 client_gravity_resize_h(client, &y, client->area.height,h);
1177
1178             client_find_onscreen(client, &x, &y, w, h, FALSE);
1179
1180             ob_debug("Granting ConfigureRequest x %d y %d w %d h %d\n",
1181                      x, y, w, h);
1182             client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
1183         }
1184         break;
1185     }
1186     case UnmapNotify:
1187         if (client->ignore_unmaps) {
1188             client->ignore_unmaps--;
1189             break;
1190         }
1191         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1192                  "ignores left %d\n",
1193                  client->window, e->xunmap.event, e->xunmap.from_configure,
1194                  client->ignore_unmaps);
1195         client_unmanage(client);
1196         break;
1197     case DestroyNotify:
1198         ob_debug("DestroyNotify for window 0x%x\n", client->window);
1199         client_unmanage(client);
1200         break;
1201     case ReparentNotify:
1202         /* this is when the client is first taken captive in the frame */
1203         if (e->xreparent.parent == client->frame->window) break;
1204
1205         /*
1206           This event is quite rare and is usually handled in unmapHandler.
1207           However, if the window is unmapped when the reparent event occurs,
1208           the window manager never sees it because an unmap event is not sent
1209           to an already unmapped window.
1210         */
1211
1212         /* we don't want the reparent event, put it back on the stack for the
1213            X server to deal with after we unmanage the window */
1214         XPutBackEvent(ob_display, e);
1215
1216         ob_debug("ReparentNotify for window 0x%x\n", client->window);
1217         client_unmanage(client);
1218         break;
1219     case MapRequest:
1220         ob_debug("MapRequest for 0x%lx\n", client->window);
1221         if (!client->iconic) break; /* this normally doesn't happen, but if it
1222                                        does, we don't want it!
1223                                        it can happen now when the window is on
1224                                        another desktop, but we still don't
1225                                        want it! */
1226         client_activate(client, FALSE, TRUE, TRUE, TRUE);
1227         break;
1228     case ClientMessage:
1229         /* validate cuz we query stuff off the client here */
1230         if (!client_validate(client)) break;
1231
1232         if (e->xclient.format != 32) return;
1233
1234         msgtype = e->xclient.message_type;
1235         if (msgtype == prop_atoms.wm_change_state) {
1236             /* compress changes into a single change */
1237             while (XCheckTypedWindowEvent(ob_display, client->window,
1238                                           e->type, &ce)) {
1239                 /* XXX: it would be nice to compress ALL messages of a
1240                    type, not just messages in a row without other
1241                    message types between. */
1242                 if (ce.xclient.message_type != msgtype) {
1243                     XPutBackEvent(ob_display, &ce);
1244                     break;
1245                 }
1246                 e->xclient = ce.xclient;
1247             }
1248             client_set_wm_state(client, e->xclient.data.l[0]);
1249         } else if (msgtype == prop_atoms.net_wm_desktop) {
1250             /* compress changes into a single change */
1251             while (XCheckTypedWindowEvent(ob_display, client->window,
1252                                           e->type, &ce)) {
1253                 /* XXX: it would be nice to compress ALL messages of a
1254                    type, not just messages in a row without other
1255                    message types between. */
1256                 if (ce.xclient.message_type != msgtype) {
1257                     XPutBackEvent(ob_display, &ce);
1258                     break;
1259                 }
1260                 e->xclient = ce.xclient;
1261             }
1262             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1263                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1264                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1265                                    FALSE, FALSE);
1266         } else if (msgtype == prop_atoms.net_wm_state) {
1267             gulong ignore_start;
1268
1269             /* can't compress these */
1270             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1271                      (e->xclient.data.l[0] == 0 ? "Remove" :
1272                       e->xclient.data.l[0] == 1 ? "Add" :
1273                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1274                      e->xclient.data.l[1], e->xclient.data.l[2],
1275                      client->window);
1276
1277             /* ignore enter events caused by these like ob actions do */
1278             if (!config_focus_under_mouse)
1279                 ignore_start = event_start_ignore_all_enters();
1280             client_set_state(client, e->xclient.data.l[0],
1281                              e->xclient.data.l[1], e->xclient.data.l[2]);
1282             if (!config_focus_under_mouse)
1283                 event_end_ignore_all_enters(ignore_start);
1284         } else if (msgtype == prop_atoms.net_close_window) {
1285             ob_debug("net_close_window for 0x%lx\n", client->window);
1286             client_close(client);
1287         } else if (msgtype == prop_atoms.net_active_window) {
1288             ob_debug("net_active_window for 0x%lx source=%s\n",
1289                      client->window,
1290                      (e->xclient.data.l[0] == 0 ? "unknown" :
1291                       (e->xclient.data.l[0] == 1 ? "application" :
1292                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1293             /* XXX make use of data.l[2] !? */
1294             if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
1295                 event_curtime = e->xclient.data.l[1];
1296                 if (event_curtime == 0)
1297                     ob_debug_type(OB_DEBUG_APP_BUGS,
1298                                   "_NET_ACTIVE_WINDOW message for window %s is"
1299                                   " missing a timestamp\n", client->title);
1300             } else
1301                 ob_debug_type(OB_DEBUG_APP_BUGS,
1302                               "_NET_ACTIVE_WINDOW message for window %s is "
1303                               "missing source indication\n");
1304             client_activate(client, FALSE, TRUE, TRUE,
1305                             (e->xclient.data.l[0] == 0 ||
1306                              e->xclient.data.l[0] == 2));
1307         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1308             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1309                      client->window, e->xclient.data.l[2]);
1310             if ((Atom)e->xclient.data.l[2] ==
1311                 prop_atoms.net_wm_moveresize_size_topleft ||
1312                 (Atom)e->xclient.data.l[2] ==
1313                 prop_atoms.net_wm_moveresize_size_top ||
1314                 (Atom)e->xclient.data.l[2] ==
1315                 prop_atoms.net_wm_moveresize_size_topright ||
1316                 (Atom)e->xclient.data.l[2] ==
1317                 prop_atoms.net_wm_moveresize_size_right ||
1318                 (Atom)e->xclient.data.l[2] ==
1319                 prop_atoms.net_wm_moveresize_size_right ||
1320                 (Atom)e->xclient.data.l[2] ==
1321                 prop_atoms.net_wm_moveresize_size_bottomright ||
1322                 (Atom)e->xclient.data.l[2] ==
1323                 prop_atoms.net_wm_moveresize_size_bottom ||
1324                 (Atom)e->xclient.data.l[2] ==
1325                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1326                 (Atom)e->xclient.data.l[2] ==
1327                 prop_atoms.net_wm_moveresize_size_left ||
1328                 (Atom)e->xclient.data.l[2] ==
1329                 prop_atoms.net_wm_moveresize_move ||
1330                 (Atom)e->xclient.data.l[2] ==
1331                 prop_atoms.net_wm_moveresize_size_keyboard ||
1332                 (Atom)e->xclient.data.l[2] ==
1333                 prop_atoms.net_wm_moveresize_move_keyboard) {
1334
1335                 moveresize_start(client, e->xclient.data.l[0],
1336                                  e->xclient.data.l[1], e->xclient.data.l[3],
1337                                  e->xclient.data.l[2]);
1338             }
1339             else if ((Atom)e->xclient.data.l[2] ==
1340                      prop_atoms.net_wm_moveresize_cancel)
1341                 moveresize_end(TRUE);
1342         } else if (msgtype == prop_atoms.net_moveresize_window) {
1343             gint ograv, x, y, w, h;
1344
1345             ograv = client->gravity;
1346
1347             if (e->xclient.data.l[0] & 0xff)
1348                 client->gravity = e->xclient.data.l[0] & 0xff;
1349
1350             if (e->xclient.data.l[0] & 1 << 8)
1351                 x = e->xclient.data.l[1];
1352             else
1353                 x = client->area.x;
1354             if (e->xclient.data.l[0] & 1 << 9)
1355                 y = e->xclient.data.l[2];
1356             else
1357                 y = client->area.y;
1358
1359             if (e->xclient.data.l[0] & 1 << 10) {
1360                 w = e->xclient.data.l[3];
1361
1362                 /* if x was not given, then use gravity to figure out the new
1363                    x.  the reference point should not be moved */
1364                 if (!(e->xclient.data.l[0] & 1 << 8))
1365                     client_gravity_resize_w(client, &x, client->area.width, w);
1366             }
1367             else
1368                 w = client->area.width;
1369
1370             if (e->xclient.data.l[0] & 1 << 11) {
1371                 h = e->xclient.data.l[4];
1372
1373                 /* if y was not given, then use gravity to figure out the new
1374                    y.  the reference point should not be moved */
1375                 if (!(e->xclient.data.l[0] & 1 << 9))
1376                     client_gravity_resize_h(client, &y, client->area.height,h);
1377             }
1378             else
1379                 h = client->area.height;
1380
1381             ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)\n",
1382                      e->xclient.data.l[0] & 1 << 8, x,
1383                      e->xclient.data.l[0] & 1 << 9, y,
1384                      client->gravity);
1385
1386             client_find_onscreen(client, &x, &y, w, h, FALSE);
1387
1388             client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1389
1390             client->gravity = ograv;
1391         } else if (msgtype == prop_atoms.net_restack_window) {
1392             if (e->xclient.data.l[0] != 2) {
1393                 ob_debug_type(OB_DEBUG_APP_BUGS,
1394                               "_NET_RESTACK_WINDOW sent for window %s with "
1395                               "invalid source indication %ld\n",
1396                               client->title, e->xclient.data.l[0]);
1397             } else {
1398                 ObClient *sibling = NULL;
1399                 if (e->xclient.data.l[1]) {
1400                     ObWindow *win = g_hash_table_lookup
1401                         (window_map, &e->xclient.data.l[1]);
1402                     if (WINDOW_IS_CLIENT(win) &&
1403                         WINDOW_AS_CLIENT(win) != client)
1404                     {
1405                         sibling = WINDOW_AS_CLIENT(win);
1406                     }
1407                     if (sibling == NULL)
1408                         ob_debug_type(OB_DEBUG_APP_BUGS,
1409                                       "_NET_RESTACK_WINDOW sent for window %s "
1410                                       "with invalid sibling 0x%x\n",
1411                                  client->title, e->xclient.data.l[1]);
1412                 }
1413                 if (e->xclient.data.l[2] == Below ||
1414                     e->xclient.data.l[2] == BottomIf ||
1415                     e->xclient.data.l[2] == Above ||
1416                     e->xclient.data.l[2] == TopIf ||
1417                     e->xclient.data.l[2] == Opposite)
1418                 {
1419                     gulong ignore_start;
1420
1421                     if (!config_focus_under_mouse)
1422                         ignore_start = event_start_ignore_all_enters();
1423                     /* just raise, don't activate */
1424                     stacking_restack_request(client, sibling,
1425                                              e->xclient.data.l[2]);
1426                     if (!config_focus_under_mouse)
1427                         event_end_ignore_all_enters(ignore_start);
1428
1429                     /* send a synthetic ConfigureNotify, cuz this is supposed
1430                        to be like a ConfigureRequest. */
1431                     client_reconfigure(client, TRUE);
1432                 } else
1433                     ob_debug_type(OB_DEBUG_APP_BUGS,
1434                                   "_NET_RESTACK_WINDOW sent for window %s "
1435                                   "with invalid detail %d\n",
1436                                   client->title, e->xclient.data.l[2]);
1437             }
1438         }
1439         break;
1440     case PropertyNotify:
1441         /* validate cuz we query stuff off the client here */
1442         if (!client_validate(client)) break;
1443
1444         /* compress changes to a single property into a single change */
1445         while (XCheckTypedWindowEvent(ob_display, client->window,
1446                                       e->type, &ce)) {
1447             Atom a, b;
1448
1449             /* XXX: it would be nice to compress ALL changes to a property,
1450                not just changes in a row without other props between. */
1451
1452             a = ce.xproperty.atom;
1453             b = e->xproperty.atom;
1454
1455             if (a == b)
1456                 continue;
1457             if ((a == prop_atoms.net_wm_name ||
1458                  a == prop_atoms.wm_name ||
1459                  a == prop_atoms.net_wm_icon_name ||
1460                  a == prop_atoms.wm_icon_name)
1461                 &&
1462                 (b == prop_atoms.net_wm_name ||
1463                  b == prop_atoms.wm_name ||
1464                  b == prop_atoms.net_wm_icon_name ||
1465                  b == prop_atoms.wm_icon_name)) {
1466                 continue;
1467             }
1468             if (a == prop_atoms.net_wm_icon &&
1469                 b == prop_atoms.net_wm_icon)
1470                 continue;
1471
1472             XPutBackEvent(ob_display, &ce);
1473             break;
1474         }
1475
1476         msgtype = e->xproperty.atom;
1477         if (msgtype == XA_WM_NORMAL_HINTS) {
1478             ob_debug("Update NORMAL hints\n");
1479             client_update_normal_hints(client);
1480             /* normal hints can make a window non-resizable */
1481             client_setup_decor_and_functions(client, FALSE);
1482
1483             /* make sure the client's sizes are within its bounds, but only
1484                reconfigure the window if it needs to. emacs will update its
1485                normal hints every time it receives a conigurenotify */
1486             client_reconfigure(client, FALSE);
1487         } else if (msgtype == XA_WM_HINTS) {
1488             client_update_wmhints(client);
1489         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1490             client_update_transient_for(client);
1491             client_get_type_and_transientness(client);
1492             /* type may have changed, so update the layer */
1493             client_calc_layer(client);
1494             client_setup_decor_and_functions(client, TRUE);
1495         } else if (msgtype == prop_atoms.net_wm_name ||
1496                    msgtype == prop_atoms.wm_name ||
1497                    msgtype == prop_atoms.net_wm_icon_name ||
1498                    msgtype == prop_atoms.wm_icon_name) {
1499             client_update_title(client);
1500         } else if (msgtype == prop_atoms.wm_protocols) {
1501             client_update_protocols(client);
1502             client_setup_decor_and_functions(client, TRUE);
1503         }
1504         else if (msgtype == prop_atoms.net_wm_strut) {
1505             client_update_strut(client);
1506         }
1507         else if (msgtype == prop_atoms.net_wm_strut_partial) {
1508             client_update_strut(client);
1509         }
1510         else if (msgtype == prop_atoms.net_wm_icon) {
1511             client_update_icons(client);
1512         }
1513         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1514             client_update_icon_geometry(client);
1515         }
1516         else if (msgtype == prop_atoms.net_wm_user_time) {
1517             client_update_user_time(client);
1518         }
1519         else if (msgtype == prop_atoms.net_wm_user_time_window) {
1520             client_update_user_time_window(client);
1521         }
1522 #ifdef SYNC
1523         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1524             client_update_sync_request_counter(client);
1525         }
1526 #endif
1527         break;
1528     case ColormapNotify:
1529         client_update_colormap(client, e->xcolormap.colormap);
1530         break;
1531     default:
1532         ;
1533 #ifdef SHAPE
1534         if (extensions_shape && e->type == extensions_shape_event_basep) {
1535             client->shaped = ((XShapeEvent*)e)->shaped;
1536             frame_adjust_shape(client->frame);
1537         }
1538 #endif
1539     }
1540 }
1541
1542 static void event_handle_dock(ObDock *s, XEvent *e)
1543 {
1544     switch (e->type) {
1545     case ButtonPress:
1546         if (e->xbutton.button == 1)
1547             stacking_raise(DOCK_AS_WINDOW(s));
1548         else if (e->xbutton.button == 2)
1549             stacking_lower(DOCK_AS_WINDOW(s));
1550         break;
1551     case EnterNotify:
1552         dock_hide(FALSE);
1553         break;
1554     case LeaveNotify:
1555         /* don't hide when moving into a dock app */
1556         if (e->xcrossing.detail != NotifyInferior)
1557             dock_hide(TRUE);
1558         break;
1559     }
1560 }
1561
1562 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1563 {
1564     switch (e->type) {
1565     case MotionNotify:
1566         dock_app_drag(app, &e->xmotion);
1567         break;
1568     case UnmapNotify:
1569         if (app->ignore_unmaps) {
1570             app->ignore_unmaps--;
1571             break;
1572         }
1573         dock_remove(app, TRUE);
1574         break;
1575     case DestroyNotify:
1576         dock_remove(app, FALSE);
1577         break;
1578     case ReparentNotify:
1579         dock_remove(app, FALSE);
1580         break;
1581     case ConfigureNotify:
1582         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1583         break;
1584     }
1585 }
1586
1587 static ObMenuFrame* find_active_menu()
1588 {
1589     GList *it;
1590     ObMenuFrame *ret = NULL;
1591
1592     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1593         ret = it->data;
1594         if (ret->selected)
1595             break;
1596         ret = NULL;
1597     }
1598     return ret;
1599 }
1600
1601 static ObMenuFrame* find_active_or_last_menu()
1602 {
1603     ObMenuFrame *ret = NULL;
1604
1605     ret = find_active_menu();
1606     if (!ret && menu_frame_visible)
1607         ret = menu_frame_visible->data;
1608     return ret;
1609 }
1610
1611 static gboolean event_handle_menu_keyboard(XEvent *ev)
1612 {
1613     guint keycode, state;
1614     gunichar unikey;
1615     ObMenuFrame *frame;
1616     gboolean ret = FALSE;
1617
1618     keycode = ev->xkey.keycode;
1619     state = ev->xkey.state;
1620     unikey = translate_unichar(keycode);
1621
1622     frame = find_active_or_last_menu();
1623     if (frame == NULL)
1624         g_assert_not_reached(); /* there is no active menu */
1625
1626     /* Allow control while going thru the menu */
1627     else if (ev->type == KeyPress && (state & ~ControlMask) == 0) {
1628         if (keycode == ob_keycode(OB_KEY_ESCAPE)) {
1629             menu_frame_hide_all();
1630             ret = TRUE;
1631         }
1632
1633         else if (keycode == ob_keycode(OB_KEY_LEFT)) {
1634             /* Left goes to the parent menu */
1635             menu_frame_select(frame, NULL, TRUE);
1636             ret = TRUE;
1637         }
1638
1639         else if (keycode == ob_keycode(OB_KEY_RIGHT)) {
1640             /* Right goes to the selected submenu */
1641             if (frame->child) menu_frame_select_next(frame->child);
1642             ret = TRUE;
1643         }
1644
1645         else if (keycode == ob_keycode(OB_KEY_UP)) {
1646             menu_frame_select_previous(frame);
1647             ret = TRUE;
1648         }
1649
1650         else if (keycode == ob_keycode(OB_KEY_DOWN)) {
1651             menu_frame_select_next(frame);
1652             ret = TRUE;
1653         }
1654     }
1655
1656     /* Use KeyRelease events for running things so that the key release doesn't
1657        get sent to the focused application.
1658
1659        Allow ControlMask only, and don't bother if the menu is empty */
1660     else if (ev->type == KeyRelease && (state & ~ControlMask) == 0 &&
1661              frame->entries)
1662     {
1663         if (keycode == ob_keycode(OB_KEY_RETURN)) {
1664             /* Enter runs the active item or goes into the submenu.
1665                Control-Enter runs it without closing the menu. */
1666             if (frame->child)
1667                 menu_frame_select_next(frame->child);
1668             else if (frame->selected)
1669                 menu_entry_frame_execute(frame->selected, state);
1670
1671             ret = TRUE;
1672         }
1673
1674         /* keyboard accelerator shortcuts. (if it was a valid key) */
1675         else if (unikey != 0) {
1676             GList *start;
1677             GList *it;
1678             ObMenuEntryFrame *found = NULL;
1679             guint num_found = 0;
1680
1681             /* start after the selected one */
1682             start = frame->entries;
1683             if (frame->selected) {
1684                 for (it = start; frame->selected != it->data;
1685                      it = g_list_next(it))
1686                     g_assert(it != NULL); /* nothing was selected? */
1687                 /* next with wraparound */
1688                 start = g_list_next(it);
1689                 if (start == NULL) start = frame->entries;
1690             }
1691
1692             it = start;
1693             do {
1694                 ObMenuEntryFrame *e = it->data;
1695                 gunichar entrykey = 0;
1696
1697                 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1698                     entrykey = e->entry->data.normal.shortcut;
1699                 else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1700                     entrykey = e->entry->data.submenu.submenu->shortcut;
1701
1702                 if (unikey == entrykey) {
1703                     if (found == NULL) found = e;
1704                     ++num_found;
1705                 }
1706
1707                 /* next with wraparound */
1708                 it = g_list_next(it);
1709                 if (it == NULL) it = frame->entries;
1710             } while (it != start);
1711
1712             if (found) {
1713                 if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1714                     num_found == 1)
1715                 {
1716                     menu_frame_select(frame, found, TRUE);
1717                     usleep(50000); /* highlight the item for a short bit so the
1718                                       user can see what happened */
1719                     menu_entry_frame_execute(found, state);
1720                 } else {
1721                     menu_frame_select(frame, found, TRUE);
1722                     if (num_found == 1)
1723                         menu_frame_select_next(frame->child);
1724                 }
1725
1726                 ret = TRUE;
1727             }
1728         }
1729     }
1730
1731     return ret;
1732 }
1733
1734 static gboolean event_handle_menu(XEvent *ev)
1735 {
1736     ObMenuFrame *f;
1737     ObMenuEntryFrame *e;
1738     gboolean ret = TRUE;
1739
1740     switch (ev->type) {
1741     case ButtonRelease:
1742         if (menu_hide_delay_reached() &&
1743             (ev->xbutton.button < 4 || ev->xbutton.button > 5))
1744         {
1745             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1746                                             ev->xbutton.y_root)))
1747             {
1748                 menu_frame_select(e->frame, e, TRUE);
1749                 menu_entry_frame_execute(e, ev->xbutton.state);
1750             }
1751             else
1752                 menu_frame_hide_all();
1753         }
1754         break;
1755     case EnterNotify:
1756         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1757             if (e->ignore_enters)
1758                 --e->ignore_enters;
1759             else if (!(f = find_active_menu()) ||
1760                      f == e->frame ||
1761                      f->parent == e->frame ||
1762                      f->child == e->frame)
1763                 menu_frame_select(e->frame, e, FALSE);
1764         }
1765         break;
1766     case LeaveNotify:
1767         /*ignore leaves when we're already in the window */
1768         if (ev->xcrossing.detail == NotifyInferior)
1769             break;
1770
1771         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1772             (f = find_active_menu()) && f->selected == e &&
1773             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1774         {
1775             menu_frame_select(e->frame, NULL, FALSE);
1776         }
1777         break;
1778     case MotionNotify:
1779         if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1780                                         ev->xmotion.y_root)))
1781             if (!(f = find_active_menu()) ||
1782                 f == e->frame ||
1783                 f->parent == e->frame ||
1784                 f->child == e->frame)
1785                 menu_frame_select(e->frame, e, FALSE);
1786         break;
1787     case KeyPress:
1788     case KeyRelease:
1789         ret = event_handle_menu_keyboard(ev);
1790         break;
1791     }
1792     return ret;
1793 }
1794
1795 static void event_handle_user_input(ObClient *client, XEvent *e)
1796 {
1797     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1798              e->type == MotionNotify || e->type == KeyPress ||
1799              e->type == KeyRelease);
1800
1801     if (menu_frame_visible) {
1802         if (event_handle_menu(e))
1803             /* don't use the event if the menu used it, but if the menu
1804                didn't use it and it's a keypress that is bound, it will
1805                close the menu and be used */
1806             return;
1807     }
1808
1809     /* if the keyboard interactive action uses the event then dont
1810        use it for bindings. likewise is moveresize uses the event. */
1811     if (!actions_interactive_input_event(e) && !moveresize_event(e)) {
1812         if (moveresize_in_progress)
1813             /* make further actions work on the client being
1814                moved/resized */
1815             client = moveresize_client;
1816
1817         if (e->type == ButtonPress ||
1818             e->type == ButtonRelease ||
1819             e->type == MotionNotify)
1820         {
1821             /* the frame may not be "visible" but they can still click on it
1822                in the case where it is animating before disappearing */
1823             if (!client || !frame_iconify_animating(client->frame))
1824                 mouse_event(client, e);
1825         } else
1826             keyboard_event((focus_cycle_target ? focus_cycle_target :
1827                             (client ? client : focus_client)), e);
1828     }
1829 }
1830
1831 static void focus_delay_dest(gpointer data)
1832 {
1833     g_free(data);
1834 }
1835
1836 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1837 {
1838     const ObFocusDelayData *f1 = d1;
1839     return f1->client == d2;
1840 }
1841
1842 static gboolean focus_delay_func(gpointer data)
1843 {
1844     ObFocusDelayData *d = data;
1845     Time old = event_curtime;
1846
1847     event_curtime = d->time;
1848     if (focus_client != d->client) {
1849         if (client_focus(d->client) && config_focus_raise)
1850             stacking_raise(CLIENT_AS_WINDOW(d->client));
1851     }
1852     event_curtime = old;
1853     return FALSE; /* no repeat */
1854 }
1855
1856 static void focus_delay_client_dest(ObClient *client, gpointer data)
1857 {
1858     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1859                                      client, FALSE);
1860 }
1861
1862 void event_halt_focus_delay()
1863 {
1864     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1865 }
1866
1867 gulong event_start_ignore_all_enters()
1868 {
1869     XSync(ob_display, FALSE);
1870     return LastKnownRequestProcessed(ob_display);
1871 }
1872
1873 void event_end_ignore_all_enters(gulong start)
1874 {
1875     ObSerialRange *r;
1876
1877     g_assert(start != 0);
1878     XSync(ob_display, FALSE);
1879
1880     r = g_new(ObSerialRange, 1);
1881     r->start = start;
1882     r->end = LastKnownRequestProcessed(ob_display);
1883     ignore_serials = g_slist_prepend(ignore_serials, r);
1884
1885     /* increment the serial so we don't ignore events we weren't meant to */
1886     XSync(ob_display, FALSE);
1887 }
1888
1889 static gboolean is_enter_focus_event_ignored(XEvent *e)
1890 {
1891     GSList *it, *next;
1892
1893     g_assert(e->type == EnterNotify &&
1894              !(e->xcrossing.mode == NotifyGrab ||
1895                e->xcrossing.mode == NotifyUngrab ||
1896                e->xcrossing.detail == NotifyInferior));
1897
1898     for (it = ignore_serials; it; it = next) {
1899         ObSerialRange *r = it->data;
1900
1901         next = g_slist_next(it);
1902
1903         if ((glong)(e->xany.serial - r->end) > 0) {
1904             /* past the end */
1905             ignore_serials = g_slist_delete_link(ignore_serials, it);
1906             g_free(r);
1907         }
1908         else if ((glong)(e->xany.serial - r->start) >= 0)
1909             return TRUE;
1910     }
1911     return FALSE;
1912 }
1913
1914 void event_cancel_all_key_grabs()
1915 {
1916     if (actions_interactive_act_running()) {
1917         actions_interactive_cancel_act();
1918         ob_debug("KILLED interactive action\n");
1919     }
1920     else if (menu_frame_visible) {
1921         menu_frame_hide_all();
1922         ob_debug("KILLED open menus\n");
1923     }
1924     else if (grab_on_keyboard()) {
1925         ungrab_keyboard();
1926         ob_debug("KILLED active grab on keyboard\n");
1927     }
1928     else
1929         ungrab_passive_key();
1930 }
1931
1932 gboolean event_time_after(Time t1, Time t2)
1933 {
1934     g_assert(t1 != CurrentTime);
1935     g_assert(t2 != CurrentTime);
1936
1937     /*
1938       Timestamp values wrap around (after about 49.7 days). The server, given
1939       its current time is represented by timestamp T, always interprets
1940       timestamps from clients by treating half of the timestamp space as being
1941       later in time than T.
1942       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1943     */
1944
1945     /* TIME_HALF is half of the number space of a Time type variable */
1946 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1947
1948     if (t2 >= TIME_HALF)
1949         /* t2 is in the second half so t1 might wrap around and be smaller than
1950            t2 */
1951         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1952     else
1953         /* t2 is in the first half so t1 has to come after it */
1954         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1955 }