+
+static void event_handle_dock(ObDock *s, XEvent *e)
+{
+ switch (e->type) {
+ case ButtonPress:
+ if (e->xbutton.button == 1)
+ stacking_raise(DOCK_AS_WINDOW(s));
+ else if (e->xbutton.button == 2)
+ stacking_lower(DOCK_AS_WINDOW(s));
+ break;
+ case EnterNotify:
+ dock_hide(FALSE);
+ break;
+ case LeaveNotify:
+ /* don't hide when moving into a dock app */
+ if (e->xcrossing.detail != NotifyInferior)
+ dock_hide(TRUE);
+ break;
+ }
+}
+
+static void event_handle_dockapp(ObDockApp *app, XEvent *e)
+{
+ switch (e->type) {
+ case MotionNotify:
+ dock_app_drag(app, &e->xmotion);
+ break;
+ case UnmapNotify:
+ if (app->ignore_unmaps) {
+ app->ignore_unmaps--;
+ break;
+ }
+ dock_remove(app, TRUE);
+ break;
+ case DestroyNotify:
+ dock_remove(app, FALSE);
+ break;
+ case ReparentNotify:
+ dock_remove(app, FALSE);
+ break;
+ case ConfigureNotify:
+ dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
+ break;
+ }
+}
+
+static ObMenuFrame* find_active_menu(void)
+{
+ GList *it;
+ ObMenuFrame *ret = NULL;
+
+ for (it = menu_frame_visible; it; it = g_list_next(it)) {
+ ret = it->data;
+ if (ret->selected)
+ break;
+ ret = NULL;
+ }
+ return ret;
+}
+
+static ObMenuFrame* find_active_or_last_menu(void)
+{
+ ObMenuFrame *ret = NULL;
+
+ ret = find_active_menu();
+ if (!ret && menu_frame_visible)
+ ret = menu_frame_visible->data;
+ return ret;
+}
+
+static gboolean event_handle_menu_keyboard(XEvent *ev)
+{
+ guint keycode, state;
+ gunichar unikey;
+ ObMenuFrame *frame;
+ gboolean ret = FALSE;
+
+ keycode = ev->xkey.keycode;
+ state = ev->xkey.state;
+ unikey = translate_unichar(keycode);
+
+ frame = find_active_or_last_menu();
+ if (frame == NULL)
+ g_assert_not_reached(); /* there is no active menu */
+
+ /* Allow control while going thru the menu */
+ else if (ev->type == KeyPress && (state & ~ControlMask) == 0) {
+ frame->got_press = TRUE;
+
+ if (keycode == ob_keycode(OB_KEY_ESCAPE)) {
+ menu_frame_hide_all();
+ ret = TRUE;
+ }
+
+ else if (keycode == ob_keycode(OB_KEY_LEFT)) {
+ /* Left goes to the parent menu */
+ menu_frame_select(frame, NULL, TRUE);
+ ret = TRUE;
+ }
+
+ else if (keycode == ob_keycode(OB_KEY_RIGHT)) {
+ /* Right goes to the selected submenu */
+ if (frame->child) menu_frame_select_next(frame->child);
+ ret = TRUE;
+ }
+
+ else if (keycode == ob_keycode(OB_KEY_UP)) {
+ menu_frame_select_previous(frame);
+ ret = TRUE;
+ }
+
+ else if (keycode == ob_keycode(OB_KEY_DOWN)) {
+ menu_frame_select_next(frame);
+ ret = TRUE;
+ }
+ }
+
+ /* Use KeyRelease events for running things so that the key release doesn't
+ get sent to the focused application.
+
+ Allow ControlMask only, and don't bother if the menu is empty */
+ else if (ev->type == KeyRelease && (state & ~ControlMask) == 0 &&
+ frame->entries && frame->got_press)
+ {
+ if (keycode == ob_keycode(OB_KEY_RETURN)) {
+ /* Enter runs the active item or goes into the submenu.
+ Control-Enter runs it without closing the menu. */
+ if (frame->child)
+ menu_frame_select_next(frame->child);
+ else if (frame->selected)
+ menu_entry_frame_execute(frame->selected, state);
+
+ ret = TRUE;
+ }
+
+ /* keyboard accelerator shortcuts. (if it was a valid key) */
+ else if (unikey != 0) {
+ GList *start;
+ GList *it;
+ ObMenuEntryFrame *found = NULL;
+ guint num_found = 0;
+
+ /* start after the selected one */
+ start = frame->entries;
+ if (frame->selected) {
+ for (it = start; frame->selected != it->data;
+ it = g_list_next(it))
+ g_assert(it != NULL); /* nothing was selected? */
+ /* next with wraparound */
+ start = g_list_next(it);
+ if (start == NULL) start = frame->entries;
+ }
+
+ it = start;
+ do {
+ ObMenuEntryFrame *e = it->data;
+ gunichar entrykey = 0;
+
+ if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
+ entrykey = e->entry->data.normal.shortcut;
+ else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
+ entrykey = e->entry->data.submenu.submenu->shortcut;
+
+ if (unikey == entrykey) {
+ if (found == NULL) found = e;
+ ++num_found;
+ }
+
+ /* next with wraparound */
+ it = g_list_next(it);
+ if (it == NULL) it = frame->entries;
+ } while (it != start);
+
+ if (found) {
+ if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
+ num_found == 1)
+ {
+ menu_frame_select(frame, found, TRUE);
+ usleep(50000); /* highlight the item for a short bit so the
+ user can see what happened */
+ menu_entry_frame_execute(found, state);
+ } else {
+ menu_frame_select(frame, found, TRUE);
+ if (num_found == 1)
+ menu_frame_select_next(frame->child);
+ }
+
+ ret = TRUE;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static gboolean event_handle_menu(XEvent *ev)
+{
+ ObMenuFrame *f;
+ ObMenuEntryFrame *e;
+ gboolean ret = TRUE;
+
+ switch (ev->type) {
+ case ButtonRelease:
+ if (menu_hide_delay_reached() &&
+ (ev->xbutton.button < 4 || ev->xbutton.button > 5))
+ {
+ if ((e = menu_entry_frame_under(ev->xbutton.x_root,
+ ev->xbutton.y_root)))
+ {
+ menu_frame_select(e->frame, e, TRUE);
+ menu_entry_frame_execute(e, ev->xbutton.state);
+ }
+ else
+ menu_frame_hide_all();
+ }
+ break;
+ case EnterNotify:
+ if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
+ if (e->ignore_enters)
+ --e->ignore_enters;
+ else if (!(f = find_active_menu()) ||
+ f == e->frame ||
+ f->parent == e->frame ||
+ f->child == e->frame)
+ menu_frame_select(e->frame, e, FALSE);
+ }
+ break;
+ case LeaveNotify:
+ /*ignore leaves when we're already in the window */
+ if (ev->xcrossing.detail == NotifyInferior)
+ break;
+
+ if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
+ (f = find_active_menu()) && f->selected == e &&
+ e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
+ {
+ menu_frame_select(e->frame, NULL, FALSE);
+ }
+ break;
+ case MotionNotify:
+ if ((e = menu_entry_frame_under(ev->xmotion.x_root,
+ ev->xmotion.y_root)))
+ if (!(f = find_active_menu()) ||
+ f == e->frame ||
+ f->parent == e->frame ||
+ f->child == e->frame)
+ menu_frame_select(e->frame, e, FALSE);
+ break;
+ case KeyPress:
+ case KeyRelease:
+ ret = event_handle_menu_keyboard(ev);
+ break;
+ }
+ return ret;
+}
+
+static void event_handle_user_input(ObClient *client, XEvent *e)
+{
+ g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
+ e->type == MotionNotify || e->type == KeyPress ||
+ e->type == KeyRelease);
+
+ if (menu_frame_visible) {
+ if (event_handle_menu(e))
+ /* don't use the event if the menu used it, but if the menu
+ didn't use it and it's a keypress that is bound, it will
+ close the menu and be used */
+ return;
+ }
+
+ /* if the keyboard interactive action uses the event then dont
+ use it for bindings. likewise is moveresize uses the event. */
+ if (!actions_interactive_input_event(e) && !moveresize_event(e)) {
+ if (moveresize_in_progress)
+ /* make further actions work on the client being
+ moved/resized */
+ client = moveresize_client;
+
+ if (e->type == ButtonPress ||
+ e->type == ButtonRelease ||
+ e->type == MotionNotify)
+ {
+ /* the frame may not be "visible" but they can still click on it
+ in the case where it is animating before disappearing */
+ if (!client || !frame_iconify_animating(client->frame))
+ mouse_event(client, e);
+ } else
+ keyboard_event((focus_cycle_target ? focus_cycle_target :
+ (client ? client : focus_client)), e);
+ }
+}
+
+static void focus_delay_dest(gpointer data)
+{
+ g_free(data);
+}
+
+static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
+{
+ const ObFocusDelayData *f1 = d1;
+ return f1->client == d2;
+}
+
+static gboolean focus_delay_func(gpointer data)
+{
+ ObFocusDelayData *d = data;
+ Time old = event_curtime;
+
+ /* don't move focus and kill the menu or the move/resize */
+ if (menu_frame_visible || moveresize_in_progress) return FALSE;
+
+ event_curtime = d->time;
+ event_curserial = d->serial;
+ if (client_focus(d->client) && config_focus_raise)
+ stacking_raise(CLIENT_AS_WINDOW(d->client));
+ event_curtime = old;
+ return FALSE; /* no repeat */
+}
+
+static void focus_delay_client_dest(ObClient *client, gpointer data)
+{
+ ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
+ client, FALSE);
+}
+
+void event_halt_focus_delay()
+{
+ /* ignore all enter events up till the event which caused this to occur */
+ if (event_curserial) event_ignore_enter_range(1, event_curserial);
+ ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
+}
+
+gulong event_start_ignore_all_enters(void)
+{
+ XSync(ob_display, FALSE);
+ return LastKnownRequestProcessed(ob_display);
+}
+
+static void event_ignore_enter_range(gulong start, gulong end)
+{
+ ObSerialRange *r;
+
+ g_assert(start != 0);
+ g_assert(end != 0);
+
+ r = g_new(ObSerialRange, 1);
+ r->start = start;
+ r->end = end;
+ ignore_serials = g_slist_prepend(ignore_serials, r);
+
+ ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu\n",
+ r->start, r->end);
+
+ /* increment the serial so we don't ignore events we weren't meant to */
+ XSync(ob_display, FALSE);
+}
+
+void event_end_ignore_all_enters(gulong start)
+{
+ XSync(ob_display, FALSE);
+ event_ignore_enter_range(start, LastKnownRequestProcessed(ob_display));
+}
+
+static gboolean is_enter_focus_event_ignored(XEvent *e)
+{
+ GSList *it, *next;
+
+ g_assert(e->type == EnterNotify &&
+ !(e->xcrossing.mode == NotifyGrab ||
+ e->xcrossing.mode == NotifyUngrab ||
+ e->xcrossing.detail == NotifyInferior));
+
+ for (it = ignore_serials; it; it = next) {
+ ObSerialRange *r = it->data;
+
+ next = g_slist_next(it);
+
+ if ((glong)(e->xany.serial - r->end) > 0) {
+ /* past the end */
+ ignore_serials = g_slist_delete_link(ignore_serials, it);
+ g_free(r);
+ }
+ else if ((glong)(e->xany.serial - r->start) >= 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void event_cancel_all_key_grabs(void)
+{
+ if (actions_interactive_act_running()) {
+ actions_interactive_cancel_act();
+ ob_debug("KILLED interactive action\n");
+ }
+ else if (menu_frame_visible) {
+ menu_frame_hide_all();
+ ob_debug("KILLED open menus\n");
+ }
+ else if (moveresize_in_progress) {
+ moveresize_end(TRUE);
+ ob_debug("KILLED interactive moveresize\n");
+ }
+ else if (grab_on_keyboard()) {
+ ungrab_keyboard();
+ ob_debug("KILLED active grab on keyboard\n");
+ }
+ else
+ ungrab_passive_key();
+}
+
+gboolean event_time_after(Time t1, Time t2)
+{
+ g_assert(t1 != CurrentTime);
+ g_assert(t2 != CurrentTime);
+
+ /*
+ Timestamp values wrap around (after about 49.7 days). The server, given
+ its current time is represented by timestamp T, always interprets
+ timestamps from clients by treating half of the timestamp space as being
+ later in time than T.
+ - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
+ */
+
+ /* TIME_HALF is half of the number space of a Time type variable */
+#define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
+
+ if (t2 >= TIME_HALF)
+ /* t2 is in the second half so t1 might wrap around and be smaller than
+ t2 */
+ return t1 >= t2 || t1 < (t2 + TIME_HALF);
+ else
+ /* t2 is in the first half so t1 has to come after it */
+ return t1 >= t2 && t1 < (t2 + TIME_HALF);
+}
+
+Time event_get_server_time(void)
+{
+ /* Generate a timestamp */
+ XEvent event;
+
+ XChangeProperty(ob_display, screen_support_win,
+ prop_atoms.wm_class, prop_atoms.string,
+ 8, PropModeAppend, NULL, 0);
+ XWindowEvent(ob_display, screen_support_win, PropertyChangeMask, &event);
+ return event.xproperty.time;
+}