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