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