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