]> Dogcows Code - chaz/openbox/blob - openbox/event.c
4398c97724d9469d1668fa68245328e30226a8f2
[chaz/openbox] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "xerror.h"
4 #include "prop.h"
5 #include "config.h"
6 #include "screen.h"
7 #include "frame.h"
8 #include "menu.h"
9 #include "framerender.h"
10 #include "focus.h"
11 #include "stacking.h"
12 #include "extensions.h"
13 #include "timer.h"
14 #include "dispatch.h"
15
16 #include <X11/Xlib.h>
17 #include <X11/keysym.h>
18 #include <X11/Xatom.h>
19 #ifdef HAVE_SYS_SELECT_H
20 # include <sys/select.h>
21 #endif
22
23 static void event_process(XEvent *e);
24 static void event_handle_root(XEvent *e);
25 static void event_handle_client(Client *c, XEvent *e);
26 static void event_handle_menu(Menu *menu, XEvent *e);
27
28 Time event_lasttime = 0;
29
30 /*! The value of the mask for the NumLock modifier */
31 unsigned int NumLockMask;
32 /*! The value of the mask for the ScrollLock modifier */
33 unsigned int ScrollLockMask;
34 /*! The key codes for the modifier keys */
35 static XModifierKeymap *modmap;
36 /*! Table of the constant modifier masks */
37 static const int mask_table[] = {
38 ShiftMask, LockMask, ControlMask, Mod1Mask,
39 Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
40 };
41 static int mask_table_size;
42
43 void event_startup()
44 {
45 mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
46
47 /* get lock masks that are defined by the display (not constant) */
48 modmap = XGetModifierMapping(ob_display);
49 g_assert(modmap);
50 if (modmap && modmap->max_keypermod > 0) {
51 size_t cnt;
52 const size_t size = mask_table_size * modmap->max_keypermod;
53 /* get the values of the keyboard lock modifiers
54 Note: Caps lock is not retrieved the same way as Scroll and Num
55 lock since it doesn't need to be. */
56 const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
57 const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
58 XK_Scroll_Lock);
59
60 for (cnt = 0; cnt < size; ++cnt) {
61 if (! modmap->modifiermap[cnt]) continue;
62
63 if (num_lock == modmap->modifiermap[cnt])
64 NumLockMask = mask_table[cnt / modmap->max_keypermod];
65 if (scroll_lock == modmap->modifiermap[cnt])
66 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
67 }
68 }
69 }
70
71 void event_shutdown()
72 {
73 XFreeModifiermap(modmap);
74 }
75
76 void event_loop()
77 {
78 fd_set selset;
79 XEvent e;
80 int x_fd;
81 struct timeval *wait;
82
83 while (TRUE) {
84 /*
85 There are slightly different event retrieval semantics here for
86 local (or high bandwidth) versus remote (or low bandwidth)
87 connections to the display/Xserver.
88 */
89 if (ob_remote) {
90 if (!XPending(ob_display))
91 break;
92 } else {
93 /*
94 This XSync allows for far more compression of events, which
95 makes things like Motion events perform far far better. Since
96 it also means network traffic for every event instead of every
97 X events (where X is the number retrieved at a time), it
98 probably should not be used for setups where Openbox is
99 running on a remote/low bandwidth display/Xserver.
100 */
101 XSync(ob_display, FALSE);
102 if (!XEventsQueued(ob_display, QueuedAlready))
103 break;
104 }
105 XNextEvent(ob_display, &e);
106
107 event_process(&e);
108 }
109
110 timer_dispatch((GTimeVal**)&wait);
111 x_fd = ConnectionNumber(ob_display);
112 FD_ZERO(&selset);
113 FD_SET(x_fd, &selset);
114 select(x_fd + 1, &selset, NULL, NULL, wait);
115 }
116
117 static Window event_get_window(XEvent *e)
118 {
119 Window window;
120
121 /* pick a window */
122 switch (e->type) {
123 case MapRequest:
124 window = e->xmap.window;
125 break;
126 case UnmapNotify:
127 window = e->xunmap.window;
128 break;
129 case DestroyNotify:
130 window = e->xdestroywindow.window;
131 break;
132 case ConfigureRequest:
133 window = e->xconfigurerequest.window;
134 break;
135 default:
136 #ifdef XKB
137 if (extensions_xkb && e->type == extensions_xkb_event_basep) {
138 switch (((XkbAnyEvent*)&e)->xkb_type) {
139 case XkbBellNotify:
140 window = ((XkbBellNotifyEvent*)&e)->window;
141 default:
142 window = None;
143 }
144 } else
145 #endif
146 window = e->xany.window;
147 }
148 return window;
149 }
150
151 static void event_set_lasttime(XEvent *e)
152 {
153 /* grab the lasttime and hack up the state */
154 switch (e->type) {
155 case ButtonPress:
156 case ButtonRelease:
157 event_lasttime = e->xbutton.time;
158 break;
159 case KeyPress:
160 event_lasttime = e->xkey.time;
161 break;
162 case KeyRelease:
163 event_lasttime = e->xkey.time;
164 break;
165 case MotionNotify:
166 event_lasttime = e->xmotion.time;
167 break;
168 case PropertyNotify:
169 event_lasttime = e->xproperty.time;
170 break;
171 case EnterNotify:
172 case LeaveNotify:
173 event_lasttime = e->xcrossing.time;
174 break;
175 default:
176 event_lasttime = CurrentTime;
177 break;
178 }
179 }
180
181 #define STRIP_MODS(s) \
182 s &= ~(LockMask | NumLockMask | ScrollLockMask), \
183 /* kill off the Button1Mask etc, only want the modifiers */ \
184 s &= (ControlMask | ShiftMask | Mod1Mask | \
185 Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
186
187 static void event_hack_mods(XEvent *e)
188 {
189 KeyCode *kp;
190 int i, k;
191
192 switch (e->type) {
193 case ButtonPress:
194 case ButtonRelease:
195 STRIP_MODS(e->xbutton.state);
196 break;
197 case KeyPress:
198 STRIP_MODS(e->xkey.state);
199 break;
200 case KeyRelease:
201 STRIP_MODS(e->xkey.state);
202 /* remove from the state the mask of the modifier being released, if
203 it is a modifier key being released (this is a little ugly..) */
204 kp = modmap->modifiermap;
205 for (i = 0; i < mask_table_size; ++i) {
206 for (k = 0; k < modmap->max_keypermod; ++k) {
207 if (*kp == e->xkey.keycode) { /* found the keycode */
208 /* remove the mask for it */
209 e->xkey.state &= ~mask_table[i];
210 /* cause the first loop to break; */
211 i = mask_table_size;
212 break; /* get outta here! */
213 }
214 ++kp;
215 }
216 }
217 break;
218 case MotionNotify:
219 STRIP_MODS(e->xmotion.state);
220 /* compress events */
221 {
222 XEvent ce;
223 while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
224 e->type, &ce)) {
225 e->xmotion.x_root = ce.xmotion.x_root;
226 e->xmotion.y_root = ce.xmotion.y_root;
227 }
228 }
229 break;
230 }
231 }
232
233 static gboolean event_ignore(XEvent *e, Client *client)
234 {
235 switch(e->type) {
236 case FocusIn:
237 #ifdef DEBUG_FOCUS
238 g_message("FocusIn on %lx mode %d detail %d", window,
239 e->xfocus.mode, e->xfocus.detail);
240 #endif
241 /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
242 because of RevertToPointerRoot. If the focus ends up reverting to
243 pointer root on a workspace change, then the FocusIn event that we
244 want will be of type NotifyAncestor. This situation does not occur
245 for FocusOut, so it is safely ignored there.
246 */
247 if (e->xfocus.detail == NotifyInferior ||
248 e->xfocus.detail > NotifyNonlinearVirtual ||
249 client == NULL) {
250 /* says a client was not found for the event (or a valid FocusIn
251 event was not found.
252 */
253 e->xfocus.window = None;
254 return TRUE;
255 }
256
257 #ifdef DEBUG_FOCUS
258 g_message("FocusIn on %lx", window);
259 #endif
260 break;
261 case FocusOut:
262 #ifdef DEBUG_FOCUS
263 g_message("FocusOut on %lx mode %d detail %d", window,
264 e->xfocus.mode, e->xfocus.detail);
265 #endif
266 if (e->xfocus.mode == NotifyGrab ||
267 e->xfocus.detail == NotifyInferior ||
268 e->xfocus.detail == NotifyAncestor ||
269 e->xfocus.detail > NotifyNonlinearVirtual) return TRUE;
270
271 #ifdef DEBUG_FOCUS
272 g_message("FocusOut on %lx", window);
273 #endif
274 /* Try process a FocusIn first, and if a legit one isn't found, then
275 do the fallback shiznit. */
276 {
277 XEvent fi, fo;
278 gboolean isfo = FALSE;
279
280 if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
281 event_process(&fi);
282
283 /* when we have gotten a fi/fo pair, then see if there are any
284 more fo's coming. if there are, then don't fallback just yet
285 */
286 if ((isfo = XCheckTypedEvent(ob_display, FocusOut, &fo)))
287 XPutBackEvent(ob_display, &fo);
288
289 /* secret magic way of event_process telling us that no client
290 was found for the FocusIn event. ^_^ */
291 if (!isfo && fi.xfocus.window == None)
292 focus_fallback(Fallback_NoFocus);
293 if (fi.xfocus.window == e->xfocus.window)
294 return TRUE;
295 } else
296 focus_fallback(Fallback_NoFocus);
297 }
298 break;
299 case EnterNotify:
300 case LeaveNotify:
301 /* NotifyUngrab occurs when a mouse button is released and the event is
302 caused, like when lowering a window */
303 if (e->xcrossing.mode == NotifyGrab ||
304 e->xcrossing.detail == NotifyInferior)
305 return TRUE;
306 break;
307 }
308 return FALSE;
309 }
310
311 static void event_process(XEvent *e)
312 {
313 Window window;
314 Client *client;
315 Menu *menu = NULL;
316
317 window = event_get_window(e);
318 if (!(client = g_hash_table_lookup(client_map, &window)))
319 menu = g_hash_table_lookup(menu_map, &window);
320 event_set_lasttime(e);
321 event_hack_mods(e);
322 if (event_ignore(e, client))
323 return;
324
325 /* deal with it in the kernel */
326 if (menu)
327 event_handle_menu(menu, e);
328 else if (client)
329 event_handle_client(client, e);
330 else if (window == ob_root)
331 event_handle_root(e);
332 else if (e->type == MapRequest)
333 client_manage(window);
334 else if (e->type == ConfigureRequest) {
335 /* unhandled configure requests must be used to configure the
336 window directly */
337 XWindowChanges xwc;
338
339 xwc.x = e->xconfigurerequest.x;
340 xwc.y = e->xconfigurerequest.y;
341 xwc.width = e->xconfigurerequest.width;
342 xwc.height = e->xconfigurerequest.height;
343 xwc.border_width = e->xconfigurerequest.border_width;
344 xwc.sibling = e->xconfigurerequest.above;
345 xwc.stack_mode = e->xconfigurerequest.detail;
346
347 /* we are not to be held responsible if someone sends us an
348 invalid request! */
349 xerror_set_ignore(TRUE);
350 XConfigureWindow(ob_display, window,
351 e->xconfigurerequest.value_mask, &xwc);
352 xerror_set_ignore(FALSE);
353 }
354
355 /* user input (action-bound) events */
356 /*
357 if (e->type == ButtonPress || e->type == ButtonRelease ||
358 e->type == MotionNotify)
359 mouse_event(e, client);
360 else if (e->type == KeyPress || e->type == KeyRelease)
361 ;
362 */
363
364 /* dispatch the event to registered handlers */
365 dispatch_x(e, client);
366 }
367
368 static void event_handle_root(XEvent *e)
369 {
370 Atom msgtype;
371
372 switch(e->type) {
373 case ClientMessage:
374 if (e->xclient.format != 32) break;
375
376 msgtype = e->xclient.message_type;
377 if (msgtype == prop_atoms.net_current_desktop) {
378 unsigned int d = e->xclient.data.l[0];
379 if (d < screen_num_desktops)
380 screen_set_desktop(d);
381 } else if (msgtype == prop_atoms.net_number_of_desktops) {
382 unsigned int d = e->xclient.data.l[0];
383 if (d > 0)
384 screen_set_num_desktops(d);
385 } else if (msgtype == prop_atoms.net_showing_desktop) {
386 screen_show_desktop(e->xclient.data.l[0] != 0);
387 }
388 break;
389 case PropertyNotify:
390 if (e->xproperty.atom == prop_atoms.net_desktop_names)
391 screen_update_desktop_names();
392 else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
393 screen_update_layout();
394 break;
395 }
396 }
397
398 static void event_handle_client(Client *client, XEvent *e)
399 {
400 XEvent ce;
401 Atom msgtype;
402 int i=0;
403
404 switch (e->type) {
405 case ButtonPress:
406 case ButtonRelease:
407 switch (frame_context(client, e->xbutton.window)) {
408 case Context_Maximize:
409 client->frame->max_press = (e->type == ButtonPress);
410 framerender_frame(client->frame);
411 break;
412 case Context_Close:
413 client->frame->close_press = (e->type == ButtonPress);
414 framerender_frame(client->frame);
415 break;
416 case Context_Iconify:
417 client->frame->iconify_press = (e->type == ButtonPress);
418 framerender_frame(client->frame);
419 break;
420 case Context_AllDesktops:
421 client->frame->desk_press = (e->type == ButtonPress);
422 framerender_frame(client->frame);
423 break;
424 case Context_Shade:
425 client->frame->shade_press = (e->type == ButtonPress);
426 framerender_frame(client->frame);
427 break;
428 default:
429 /* nothing changes with clicks for any other contexts */
430 break;
431 }
432 break;
433 case FocusIn:
434 focus_set_client(client);
435 case FocusOut:
436 #ifdef DEBUG_FOCUS
437 g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
438 client->window);
439 #endif
440 /* focus state can affect the stacking layer */
441 client_calc_layer(client);
442 frame_adjust_focus(client->frame);
443 break;
444 case EnterNotify:
445 if (client_normal(client)) {
446 if (ob_state == State_Starting) {
447 /* move it to the top of the focus order */
448 guint desktop = client->desktop;
449 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
450 focus_order[desktop] = g_list_remove(focus_order[desktop],
451 client);
452 focus_order[desktop] = g_list_prepend(focus_order[desktop],
453 client);
454 } else if (config_focus_follow) {
455 #ifdef DEBUG_FOCUS
456 g_message("EnterNotify on %lx, focusing window",
457 client->window);
458 #endif
459 client_focus(client);
460 }
461 }
462 break;
463 case ConfigureRequest:
464 /* compress these */
465 while (XCheckTypedWindowEvent(ob_display, client->window,
466 ConfigureRequest, &ce)) {
467 ++i;
468 /* XXX if this causes bad things.. we can compress config req's
469 with the same mask. */
470 e->xconfigurerequest.value_mask |=
471 ce.xconfigurerequest.value_mask;
472 if (ce.xconfigurerequest.value_mask & CWX)
473 e->xconfigurerequest.x = ce.xconfigurerequest.x;
474 if (ce.xconfigurerequest.value_mask & CWY)
475 e->xconfigurerequest.y = ce.xconfigurerequest.y;
476 if (ce.xconfigurerequest.value_mask & CWWidth)
477 e->xconfigurerequest.width = ce.xconfigurerequest.width;
478 if (ce.xconfigurerequest.value_mask & CWHeight)
479 e->xconfigurerequest.height = ce.xconfigurerequest.height;
480 if (ce.xconfigurerequest.value_mask & CWBorderWidth)
481 e->xconfigurerequest.border_width =
482 ce.xconfigurerequest.border_width;
483 if (ce.xconfigurerequest.value_mask & CWStackMode)
484 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
485 }
486
487 /* if we are iconic (or shaded (fvwm does this)) ignore the event */
488 if (client->iconic || client->shaded) return;
489
490 if (e->xconfigurerequest.value_mask & CWBorderWidth)
491 client->border_width = e->xconfigurerequest.border_width;
492
493 /* resize, then move, as specified in the EWMH section 7.7 */
494 if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
495 CWX | CWY)) {
496 int x, y, w, h;
497 Corner corner;
498
499 x = (e->xconfigurerequest.value_mask & CWX) ?
500 e->xconfigurerequest.x : client->area.x;
501 y = (e->xconfigurerequest.value_mask & CWY) ?
502 e->xconfigurerequest.y : client->area.y;
503 w = (e->xconfigurerequest.value_mask & CWWidth) ?
504 e->xconfigurerequest.width : client->area.width;
505 h = (e->xconfigurerequest.value_mask & CWHeight) ?
506 e->xconfigurerequest.height : client->area.height;
507
508 switch (client->gravity) {
509 case NorthEastGravity:
510 case EastGravity:
511 corner = Corner_TopRight;
512 break;
513 case SouthWestGravity:
514 case SouthGravity:
515 corner = Corner_BottomLeft;
516 break;
517 case SouthEastGravity:
518 corner = Corner_BottomRight;
519 break;
520 default: /* NorthWest, Static, etc */
521 corner = Corner_TopLeft;
522 }
523
524 client_configure(client, corner, x, y, w, h, FALSE, FALSE);
525 }
526
527 if (e->xconfigurerequest.value_mask & CWStackMode) {
528 switch (e->xconfigurerequest.detail) {
529 case Below:
530 case BottomIf:
531 stacking_lower(client);
532 break;
533
534 case Above:
535 case TopIf:
536 default:
537 stacking_raise(client);
538 break;
539 }
540 }
541 break;
542 case UnmapNotify:
543 if (client->ignore_unmaps) {
544 client->ignore_unmaps--;
545 break;
546 }
547 client_unmanage(client);
548 break;
549 case DestroyNotify:
550 client_unmanage(client);
551 break;
552 case ReparentNotify:
553 /* this is when the client is first taken captive in the frame */
554 if (e->xreparent.parent == client->frame->plate) break;
555
556 /*
557 This event is quite rare and is usually handled in unmapHandler.
558 However, if the window is unmapped when the reparent event occurs,
559 the window manager never sees it because an unmap event is not sent
560 to an already unmapped window.
561 */
562
563 /* we don't want the reparent event, put it back on the stack for the
564 X server to deal with after we unmanage the window */
565 XPutBackEvent(ob_display, e);
566
567 client_unmanage(client);
568 break;
569 case MapRequest:
570 g_message("MapRequest for 0x%lx", client->window);
571 if (!client->iconic) break; /* this normally doesn't happen, but if it
572 does, we don't want it! */
573 if (screen_showing_desktop)
574 screen_show_desktop(FALSE);
575 client_iconify(client, FALSE, TRUE);
576 if (!client->frame->visible)
577 /* if its not visible still, then don't mess with it */
578 break;
579 if (client->shaded)
580 client_shade(client, FALSE);
581 client_focus(client);
582 stacking_raise(client);
583 break;
584 case ClientMessage:
585 /* validate cuz we query stuff off the client here */
586 if (!client_validate(client)) break;
587
588 if (e->xclient.format != 32) return;
589
590 msgtype = e->xclient.message_type;
591 if (msgtype == prop_atoms.wm_change_state) {
592 /* compress changes into a single change */
593 while (XCheckTypedWindowEvent(ob_display, e->type,
594 client->window, &ce)) {
595 /* XXX: it would be nice to compress ALL messages of a
596 type, not just messages in a row without other
597 message types between. */
598 if (ce.xclient.message_type != msgtype) {
599 XPutBackEvent(ob_display, &ce);
600 break;
601 }
602 e->xclient = ce.xclient;
603 }
604 client_set_wm_state(client, e->xclient.data.l[0]);
605 } else if (msgtype == prop_atoms.net_wm_desktop) {
606 /* compress changes into a single change */
607 while (XCheckTypedWindowEvent(ob_display, e->type,
608 client->window, &ce)) {
609 /* XXX: it would be nice to compress ALL messages of a
610 type, not just messages in a row without other
611 message types between. */
612 if (ce.xclient.message_type != msgtype) {
613 XPutBackEvent(ob_display, &ce);
614 break;
615 }
616 e->xclient = ce.xclient;
617 }
618 if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
619 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
620 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
621 FALSE);
622 } else if (msgtype == prop_atoms.net_wm_state) {
623 /* can't compress these */
624 g_message("net_wm_state %s %ld %ld for 0x%lx",
625 (e->xclient.data.l[0] == 0 ? "Remove" :
626 e->xclient.data.l[0] == 1 ? "Add" :
627 e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
628 e->xclient.data.l[1], e->xclient.data.l[2],
629 client->window);
630 client_set_state(client, e->xclient.data.l[0],
631 e->xclient.data.l[1], e->xclient.data.l[2]);
632 } else if (msgtype == prop_atoms.net_close_window) {
633 g_message("net_close_window for 0x%lx", client->window);
634 client_close(client);
635 } else if (msgtype == prop_atoms.net_active_window) {
636 g_message("net_active_window for 0x%lx", client->window);
637 if (screen_showing_desktop)
638 screen_show_desktop(FALSE);
639 if (client->iconic)
640 client_iconify(client, FALSE, TRUE);
641 else if (!client->frame->visible)
642 /* if its not visible for other reasons, then don't mess
643 with it */
644 break;
645 if (client->shaded)
646 client_shade(client, FALSE);
647 client_focus(client);
648 stacking_raise(client);
649 }
650 break;
651 case PropertyNotify:
652 /* validate cuz we query stuff off the client here */
653 if (!client_validate(client)) break;
654
655 /* compress changes to a single property into a single change */
656 while (XCheckTypedWindowEvent(ob_display, e->type,
657 client->window, &ce)) {
658 /* XXX: it would be nice to compress ALL changes to a property,
659 not just changes in a row without other props between. */
660 if (ce.xproperty.atom != e->xproperty.atom) {
661 XPutBackEvent(ob_display, &ce);
662 break;
663 }
664 }
665
666 msgtype = e->xproperty.atom;
667 if (msgtype == XA_WM_NORMAL_HINTS) {
668 client_update_normal_hints(client);
669 /* normal hints can make a window non-resizable */
670 client_setup_decor_and_functions(client);
671 }
672 else if (msgtype == XA_WM_HINTS)
673 client_update_wmhints(client);
674 else if (msgtype == XA_WM_TRANSIENT_FOR) {
675 client_update_transient_for(client);
676 client_get_type(client);
677 /* type may have changed, so update the layer */
678 client_calc_layer(client);
679 client_setup_decor_and_functions(client);
680 }
681 else if (msgtype == prop_atoms.net_wm_name ||
682 msgtype == prop_atoms.wm_name)
683 client_update_title(client);
684 else if (msgtype == prop_atoms.net_wm_icon_name ||
685 msgtype == prop_atoms.wm_icon_name)
686 client_update_icon_title(client);
687 else if (msgtype == prop_atoms.wm_class)
688 client_update_class(client);
689 else if (msgtype == prop_atoms.wm_protocols) {
690 client_update_protocols(client);
691 client_setup_decor_and_functions(client);
692 }
693 else if (msgtype == prop_atoms.net_wm_strut)
694 client_update_strut(client);
695 else if (msgtype == prop_atoms.net_wm_icon)
696 client_update_icons(client);
697 else if (msgtype == prop_atoms.kwm_win_icon)
698 client_update_kwm_icon(client);
699 default:
700 ;
701 #ifdef SHAPE
702 if (extensions_shape && e->type == extensions_shape_event_basep) {
703 client->shaped = ((XShapeEvent*)e)->shaped;
704 frame_adjust_shape(client->frame);
705 }
706 #endif
707 }
708 }
709
710 static void event_handle_menu(Menu *menu, XEvent *e)
711 {
712 MenuEntry *entry;
713
714 switch (e->type) {
715 case EnterNotify:
716 case LeaveNotify:
717 g_message("enter/leave");
718 entry = menu_find_entry(menu, e->xcrossing.window);
719 if (entry) {
720 entry->hilite = e->type == EnterNotify;
721 menu_entry_render(entry);
722 }
723 break;
724 }
725 }
This page took 0.063633 seconds and 3 git commands to generate.