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