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