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