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