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