+ /* compress changes to a single property into a single change */
+ while (XCheckTypedWindowEvent(ob_display, client->window,
+ e->type, &ce)) {
+ Atom a, b;
+
+ /* XXX: it would be nice to compress ALL changes to a property,
+ not just changes in a row without other props between. */
+
+ a = ce.xproperty.atom;
+ b = e->xproperty.atom;
+
+ if (a == b)
+ continue;
+ if ((a == prop_atoms.net_wm_name ||
+ a == prop_atoms.wm_name ||
+ a == prop_atoms.net_wm_icon_name ||
+ a == prop_atoms.wm_icon_name)
+ &&
+ (b == prop_atoms.net_wm_name ||
+ b == prop_atoms.wm_name ||
+ b == prop_atoms.net_wm_icon_name ||
+ b == prop_atoms.wm_icon_name)) {
+ continue;
+ }
+ if (a == prop_atoms.net_wm_icon &&
+ b == prop_atoms.net_wm_icon)
+ continue;
+
+ XPutBackEvent(ob_display, &ce);
+ break;
+ }
+
+ msgtype = e->xproperty.atom;
+ if (msgtype == XA_WM_NORMAL_HINTS) {
+ client_update_normal_hints(client);
+ /* normal hints can make a window non-resizable */
+ client_setup_decor_and_functions(client);
+ } else if (msgtype == XA_WM_HINTS) {
+ client_update_wmhints(client);
+ } else if (msgtype == XA_WM_TRANSIENT_FOR) {
+ client_update_transient_for(client);
+ client_get_type_and_transientness(client);
+ /* type may have changed, so update the layer */
+ client_calc_layer(client);
+ client_setup_decor_and_functions(client);
+ } else if (msgtype == prop_atoms.net_wm_name ||
+ msgtype == prop_atoms.wm_name ||
+ msgtype == prop_atoms.net_wm_icon_name ||
+ msgtype == prop_atoms.wm_icon_name) {
+ client_update_title(client);
+ } else if (msgtype == prop_atoms.wm_protocols) {
+ client_update_protocols(client);
+ client_setup_decor_and_functions(client);
+ }
+ else if (msgtype == prop_atoms.net_wm_strut) {
+ client_update_strut(client);
+ }
+ else if (msgtype == prop_atoms.net_wm_icon) {
+ client_update_icons(client);
+ }
+ else if (msgtype == prop_atoms.net_wm_icon_geometry) {
+ client_update_icon_geometry(client);
+ }
+ else if (msgtype == prop_atoms.net_wm_user_time) {
+ client_update_user_time(client);
+ }
+ else if (msgtype == prop_atoms.net_wm_user_time_window) {
+ client_update_user_time_window(client);
+ }
+#ifdef SYNC
+ else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
+ client_update_sync_request_counter(client);
+ }
+#endif
+ case ColormapNotify:
+ client_update_colormap(client, e->xcolormap.colormap);
+ break;
+ default:
+ ;
+#ifdef SHAPE
+ if (extensions_shape && e->type == extensions_shape_event_basep) {
+ client->shaped = ((XShapeEvent*)e)->shaped;
+ frame_adjust_shape(client->frame);
+ }
+#endif
+ }
+}
+
+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:
+ 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()
+{
+ 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()
+{
+ 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 = TRUE;
+
+ keycode = ev->xkey.keycode;
+ state = ev->xkey.state;
+ unikey = translate_unichar(keycode);
+
+ frame = find_active_or_last_menu();
+ if (frame == NULL)
+ ret = FALSE;
+
+ else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
+ /* Escape goes to the parent menu or closes the last one */
+ if (frame->parent)
+ menu_frame_select(frame, NULL, TRUE);
+ else
+ menu_frame_hide_all();
+ }
+
+ else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
+ state == ControlMask))
+ {
+ /* 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
+ menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
+ }
+
+ else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
+ /* Left goes to the parent menu */
+ menu_frame_select(frame, NULL, TRUE);
+ }
+
+ else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
+ /* Right goes to the selected submenu */
+ if (frame->child) menu_frame_select_next(frame->child);
+ }
+
+ else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
+ menu_frame_select_previous(frame);
+ }
+
+ else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
+ menu_frame_select_next(frame);
+ }
+
+ /* keyboard accelerator shortcuts. */
+ else if (ev->xkey.state == 0 &&
+ /* was it a valid key? */
+ unikey != 0 &&
+ /* don't bother if the menu is empty. */
+ frame->entries)
+ {
+ 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, ev->xkey.time);
+ } else {
+ menu_frame_select(frame, found, TRUE);
+ if (num_found == 1)
+ menu_frame_select_next(frame->child);
+ }
+ } else
+ ret = FALSE;
+ }
+ else
+ ret = FALSE;
+
+ return ret;
+}
+
+static gboolean event_handle_menu(XEvent *ev)
+{
+ ObMenuFrame *f;
+ ObMenuEntryFrame *e;
+ gboolean ret = TRUE;
+
+ switch (ev->type) {
+ case ButtonRelease:
+ if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
+ && menu_can_hide)
+ {
+ if ((e = menu_entry_frame_under(ev->xbutton.x_root,
+ ev->xbutton.y_root)))
+ menu_entry_frame_execute(e, ev->xbutton.state,
+ ev->xbutton.time);
+ 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
+ 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)))
+ menu_frame_select(e->frame, e, FALSE);
+ break;
+ case KeyPress:
+ 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 (!keyboard_process_interactive_grab(e, &client) &&
+ !(moveresize_in_progress && moveresize_event(e)))
+ {
+ if (moveresize_in_progress)
+ /* make further actions work on the client being
+ moved/resized */
+ client = moveresize_client;
+
+ menu_can_hide = FALSE;
+ ob_main_loop_timeout_add(ob_main_loop,
+ config_menu_hide_delay * 1000,
+ menu_hide_delay_func,
+ NULL, g_direct_equal, NULL);
+
+ 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 if (e->type == KeyPress) {
+ keyboard_event((focus_cycle_target ? focus_cycle_target :
+ (client ? client : focus_client)), e);
+ }
+ }
+}
+
+static gboolean menu_hide_delay_func(gpointer data)
+{
+ menu_can_hide = TRUE;
+ return FALSE; /* no repeat */
+}
+
+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;
+
+ event_curtime = d->time;
+ if (focus_client != d->client) {
+ 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()
+{
+ ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
+}
+
+static Bool event_look_for_enters(Display *d, XEvent *e, XPointer arg)
+{
+ if (e->type == EnterNotify &&
+ /* these types aren't used for focusing */
+ !(e->xcrossing.mode == NotifyGrab ||
+ e->xcrossing.mode == NotifyUngrab ||
+ e->xcrossing.detail == NotifyInferior))
+ {
+ ObWindow *win;
+
+ /* found an enter for that leave, ignore it if it's going to
+ another window */
+ win = g_hash_table_lookup(window_map, &e->xany.window);
+ if (win && WINDOW_IS_CLIENT(win))
+ ++ignore_enter_focus;