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