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