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