diff --git a/src/display-x11.cc b/src/display-x11.cc index 93b1a1733..78a9144e6 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -374,7 +374,7 @@ bool display_output_x11::main_loop_wait(double t) { } } - process_surface_events(display); + process_surface_events(this, display); #ifdef BUILD_XDAMAGE if (x11_stuff.damage) { @@ -420,273 +420,329 @@ bool display_output_x11::main_loop_wait(double t) { return true; } -void display_output_x11::process_surface_events(Display *display) { - int pending = XPending(display); - if (pending == 0) { - return; - } - - DBGP2("Processing %d X11 events...", pending); +#define EV_HANDLER(name) \ + bool _conky_ev_handle_##name(conky::display_output_x11 *surface, \ + Display *display, XEvent &ev, bool *consumed, \ + bool *focus) - /* handle X events */ - while (XPending(display) != 0) { - XEvent ev; - /* indicates whether processed event was consumed */ - bool consumed = false; +#ifdef OWN_WINDOW +#ifdef BUILD_MOUSE_EVENTS +#ifdef BUILD_XINPUT +EV_HANDLER(mouse_input) { + if (ev.type != GenericEvent || ev.xcookie.extension != window.xi_opcode) + return false; - XNextEvent(display, &ev); + if (!XGetEventData(display, &ev.xcookie)) { + NORM_ERR("unable to get XInput event data"); + return false; + } -#if defined(OWN_WINDOW) && defined(BUILD_MOUSE_EVENTS) && defined(BUILD_XINPUT) - // no need to check whether these events have been consumed because - // they're global and shouldn't be propagated - if (ev.type == GenericEvent && ev.xcookie.extension == window.xi_opcode) { - if (!XGetEventData(display, &ev.xcookie)) { - NORM_ERR("unable to get XInput event data"); - continue; - } + auto *data = reinterpret_cast(ev.xcookie.data); + + // the only way to differentiate between a scroll and move event is + // though valuators - move has first 2 set, other axis movements have + // other. + bool is_cursor_move = + data->valuators.mask_len >= 1 && + (data->valuators.mask[0] & 3) == data->valuators.mask[0]; + for (std::size_t i = 1; i < data->valuators.mask_len; i++) { + if (data->valuators.mask[i] != 0) { + is_cursor_move = false; + break; + } + } - auto *data = reinterpret_cast(ev.xcookie.data); - - // the only way to differentiate between a scroll and move event is - // though valuators - move has first 2 set, other axis movements have - // other. - bool is_cursor_move = - data->valuators.mask_len >= 1 && - (data->valuators.mask[0] & 3) == data->valuators.mask[0]; - for (std::size_t i = 1; i < data->valuators.mask_len; i++) { - if (data->valuators.mask[i] != 0) { - is_cursor_move = false; - break; - } + if (data->evtype == XI_Motion && is_cursor_move) { + Window query_result = + query_x11_window_at_pos(display, data->root_x, data->root_y); + // query_result is not window.window in some cases. + query_result = query_x11_last_descendant(display, query_result); + + static bool cursor_inside = false; + + // - over conky window + // - conky has now window, over desktop and within conky region + bool cursor_over_conky = + query_result == window.window && + (window.window != 0u || (data->root_x >= window.x && + data->root_x < (window.x + window.width) && + data->root_y >= window.y && + data->root_y < (window.y + window.height))); + if (cursor_over_conky) { + if (!cursor_inside) { + *consumed = llua_mouse_hook(mouse_crossing_event( + mouse_event_t::AREA_ENTER, data->root_x - window.x, + data->root_y - window.x, data->root_x, data->root_y)); } + cursor_inside = true; + } else if (cursor_inside) { + *consumed = llua_mouse_hook(mouse_crossing_event( + mouse_event_t::AREA_LEAVE, data->root_x - window.x, + data->root_y - window.x, data->root_x, data->root_y)); + cursor_inside = false; + } + } + // TODO: Handle other events - if (data->evtype == XI_Motion && is_cursor_move) { - Window query_result = - query_x11_window_at_pos(display, data->root_x, data->root_y); - // query_result is not window.window in some cases. - query_result = query_x11_last_descendant(display, query_result); - - static bool cursor_inside = false; - - // - over conky window - // - conky has now window, over desktop and within conky region - bool cursor_over_conky = query_result == window.window && - (window.window != 0u || - (data->root_x >= window.x && - data->root_x < (window.x + window.width) && - data->root_y >= window.y && - data->root_y < (window.y + window.height))); - if (cursor_over_conky) { - if (!cursor_inside) { - llua_mouse_hook(mouse_crossing_event( - mouse_event_t::AREA_ENTER, data->root_x - window.x, - data->root_y - window.x, data->root_x, data->root_y)); - } - cursor_inside = true; - } else if (cursor_inside) { - llua_mouse_hook(mouse_crossing_event( - mouse_event_t::AREA_LEAVE, data->root_x - window.x, - data->root_y - window.x, data->root_x, data->root_y)); - cursor_inside = false; - } + XFreeEventData(display, &ev.xcookie); + return true; +} +#else /* BUILD_XINPUT */ +EV_HANDLER(mouse_input) { + if (ev.type != ButtonPress && ev.type != ButtonRelease && + ev.type != MotionNotify) + return false; + if (ev.xany.window != window.window) return true; // Skip other windows + + switch (ev.type) { + case ButtonPress: { + modifier_state_t mods = x11_modifier_state(ev.xbutton.state); + if (ev.xbutton.button >= 4 && + ev.xbutton.button <= 7) { // scroll "buttons" + scroll_direction_t direction = x11_scroll_direction(ev.xbutton.button); + *consumed = llua_mouse_hook( + mouse_scroll_event(ev.xbutton.x, ev.xbutton.y, ev.xbutton.x_root, + ev.xbutton.y_root, direction, mods)); + } else { + mouse_button_t button = x11_mouse_button_code(ev.xbutton.button); + *consumed = llua_mouse_hook(mouse_button_event( + mouse_event_t::MOUSE_PRESS, ev.xbutton.x, ev.xbutton.y, + ev.xbutton.x_root, ev.xbutton.y_root, button, mods)); } - XFreeEventData(display, &ev.xcookie); - continue; + return true; + } + case ButtonRelease: { + /* don't report scroll release events */ + if (ev.xbutton.button >= 4 && ev.xbutton.button <= 7) return true; + + modifier_state_t mods = x11_modifier_state(ev.xbutton.state); + mouse_button_t button = x11_mouse_button_code(ev.xbutton.button); + *consumed = llua_mouse_hook(mouse_button_event( + mouse_event_t::MOUSE_RELEASE, ev.xbutton.x, ev.xbutton.y, + ev.xbutton.x_root, ev.xbutton.y_root, button, mods)); + return true; } -#endif /* BUILD_MOUSE_EVENTS && BUILD_XINPUT */ - - // Any of the remaining events apply to conky window - if (ev.xany.window != window.window && ev.type != PropertyNotify) continue; - switch (ev.type) { - case Expose: { - XRectangle r; - r.x = ev.xexpose.x; - r.y = ev.xexpose.y; - r.width = ev.xexpose.width; - r.height = ev.xexpose.height; - XUnionRectWithRegion(&r, x11_stuff.region, x11_stuff.region); - XSync(display, False); + case MotionNotify: { + modifier_state_t mods = x11_modifier_state(ev.xmotion.state); + *consumed = llua_mouse_hook(mouse_move_event(ev.xmotion.x, ev.xmotion.y, + ev.xmotion.x_root, + ev.xmotion.y_root, mods)); + return true; + } + default: + return false; // unreachable + } +} +#endif /* BUILD_XINPUT */ +#else /* BUILD_MOUSE_EVENTS */ +EV_HANDLER(mouse_input) { + if (ev.type != ButtonPress && ev.type != ButtonRelease && + ev.type != MotionNotify) + return false; + if (ev.xany.window != window.window) return true; // Skip other windows + // always propagate mouse input if not handling mouse events + *consumed = false; + + // don't focus if not a click + if (ev.type != ButtonPress) return true; + + // skip if not own_window + if (!own_window.get(*state)) return true; + + // don't focus if normal but undecorated + if (own_window_type.get(*state) == TYPE_NORMAL && + TEST_HINT(own_window_hints.get(*state), HINT_UNDECORATED)) + return true; - continue; - } + // don't force focus if desktop window + if (own_window_type.get(*state) == TYPE_DESKTOP) return true; + + // finally, else focus + *focus = true; + + return true; +} +#endif /* BUILD_MOUSE_EVENTS */ +#endif /* OWN_WINDOW */ +EV_HANDLER(property_notify) { + if (ev.type != PropertyNotify) return false; + + if (ev.xproperty.state == PropertyNewValue) { + get_x11_desktop_info(ev.xproperty.display, ev.xproperty.atom); + } - case PropertyNotify: { - if (ev.xproperty.state == PropertyNewValue) { - get_x11_desktop_info(ev.xproperty.display, ev.xproperty.atom); - } -#ifdef USE_ARGB - if (!have_argb_visual) { -#endif - if (ev.xproperty.atom == ATOM(_XROOTPMAP_ID) || - ev.xproperty.atom == ATOM(_XROOTMAP_ID)) { - if (forced_redraw.get(*state)) { - draw_stuff(); - next_update_time = get_time(); - need_to_update = 1; - } - } #ifdef USE_ARGB - } + if (have_argb_visual) return true; #endif - continue; - } -#ifdef OWN_WINDOW - case ReparentNotify: - /* make background transparent */ - if (own_window.get(*state)) { - set_transparent_background(window.window); - } - continue; + if (ev.xproperty.atom == ATOM(_XROOTPMAP_ID) || + ev.xproperty.atom == ATOM(_XROOTMAP_ID)) { + if (forced_redraw.get(*state)) { + draw_stuff(); + next_update_time = get_time(); + need_to_update = 1; + } + } + return true; +} - case ConfigureNotify: - if (own_window.get(*state)) { - /* if window size isn't what expected, set fixed size */ - if (ev.xconfigure.width != window.width || - ev.xconfigure.height != window.height) { - if (window.width != 0 && window.height != 0) { fixed_size = 1; } - - /* clear old stuff before screwing up - * size and pos */ - clear_text(1); - - { - XWindowAttributes attrs; - if (XGetWindowAttributes(display, window.window, &attrs) != 0) { - window.width = attrs.width; - window.height = attrs.height; - } - } - - int border_total = get_border_total(); - - text_width = window.width - 2 * border_total; - text_height = window.height - 2 * border_total; - int mw = this->dpi_scale(maximum_width.get(*state)); - if (text_width > mw && mw > 0) { text_width = mw; } - } +EV_HANDLER(expose) { + if (ev.type != Expose) return false; - /* if position isn't what expected, set fixed pos - * total_updates avoids setting fixed_pos when window - * is set to weird locations when started */ - /* // this is broken - if (total_updates >= 2 && !fixed_pos - && (window.x != ev.xconfigure.x - || window.y != ev.xconfigure.y) - && (ev.xconfigure.x != 0 - || ev.xconfigure.y != 0)) { - fixed_pos = 1; - } */ - } - continue; + XRectangle r; + r.x = ev.xexpose.x; + r.y = ev.xexpose.y; + r.width = ev.xexpose.width; + r.height = ev.xexpose.height; + XUnionRectWithRegion(&r, x11_stuff.region, x11_stuff.region); + XSync(display, False); + return true; +} + +EV_HANDLER(reparent) { + if (ev.type != ReparentNotify) return false; + + if (own_window.get(*state)) { set_transparent_background(window.window); } + return true; +} + +EV_HANDLER(configure) { + if (ev.type != ConfigureNotify) return false; + + if (own_window.get(*state)) { + /* if window size isn't what expected, set fixed size */ + if (ev.xconfigure.width != window.width || + ev.xconfigure.height != window.height) { + if (window.width != 0 && window.height != 0) { fixed_size = 1; } + + /* clear old stuff before screwing up + * size and pos */ + surface->clear_text(1); - case ButtonPress: -#ifdef BUILD_MOUSE_EVENTS { - modifier_state_t mods = x11_modifier_state(ev.xbutton.state); - if (4 <= ev.xbutton.button && ev.xbutton.button <= 7) { - scroll_direction_t direction = - x11_scroll_direction(ev.xbutton.button); - consumed = llua_mouse_hook( - mouse_scroll_event(ev.xbutton.x, ev.xbutton.y, ev.xbutton.x_root, - ev.xbutton.y_root, direction, mods)); - } else { - mouse_button_t button = x11_mouse_button_code(ev.xbutton.button); - consumed = llua_mouse_hook(mouse_button_event( - mouse_event_t::MOUSE_PRESS, ev.xbutton.x, ev.xbutton.y, - ev.xbutton.x_root, ev.xbutton.y_root, button, mods)); + XWindowAttributes attrs; + if (XGetWindowAttributes(display, window.window, &attrs) != 0) { + window.width = attrs.width; + window.height = attrs.height; } } -#endif /* BUILD_MOUSE_EVENTS */ - if (own_window.get(*state)) { - /* if an ordinary window with decorations */ - if ((own_window_type.get(*state) == TYPE_NORMAL && - !TEST_HINT(own_window_hints.get(*state), HINT_UNDECORATED)) || - own_window_type.get(*state) == TYPE_DESKTOP) { - /* allow conky to hold input focus. */ - break; - } - } - break; - case ButtonRelease: -#ifdef BUILD_MOUSE_EVENTS - /* don't report scroll release events */ - if (4 > ev.xbutton.button || ev.xbutton.button > 7) { - modifier_state_t mods = x11_modifier_state(ev.xbutton.state); - mouse_button_t button = x11_mouse_button_code(ev.xbutton.button); - consumed = llua_mouse_hook(mouse_button_event( - mouse_event_t::MOUSE_RELEASE, ev.xbutton.x, ev.xbutton.y, - ev.xbutton.x_root, ev.xbutton.y_root, button, mods)); - } -#endif /* BUILD_MOUSE_EVENTS */ - if (own_window.get(*state)) { - /* if an ordinary window with decorations */ - if ((own_window_type.get(*state) == TYPE_NORMAL) && - !TEST_HINT(own_window_hints.get(*state), HINT_UNDECORATED)) { - /* allow conky to hold input focus. */ - break; - } - } - break; -#ifdef BUILD_MOUSE_EVENTS - /* - windows below are notified for the following events as well; - can't forward the event without filtering XQueryTree output. - */ - case MotionNotify: { - modifier_state_t mods = x11_modifier_state(ev.xmotion.state); - consumed = llua_mouse_hook(mouse_move_event(ev.xmotion.x, ev.xmotion.y, - ev.xmotion.x_root, - ev.xmotion.y_root, mods)); - } break; - case LeaveNotify: - case EnterNotify: - if (window.xi_opcode == 0) { - bool not_over_conky = - ev.xcrossing.x_root <= window.x || - ev.xcrossing.y_root <= window.y || - ev.xcrossing.x_root >= window.x + window.width || - ev.xcrossing.y_root >= window.y + window.height; - - if ((not_over_conky && ev.xcrossing.type == LeaveNotify) || - (!not_over_conky && ev.xcrossing.type == EnterNotify)) { - llua_mouse_hook(mouse_crossing_event( - ev.xcrossing.type == EnterNotify ? mouse_event_t::AREA_ENTER - : mouse_event_t::AREA_LEAVE, - ev.xcrossing.x, ev.xcrossing.y, ev.xcrossing.x_root, - ev.xcrossing.y_root)); - } - } - // can't propagate these events in a way that makes sense for desktop - continue; -#endif /* BUILD_MOUSE_EVENTS */ -#endif /* OWN_WINDOW */ - default: + int border_total = get_border_total(); + + text_width = window.width - 2 * border_total; + text_height = window.height - 2 * border_total; + int mw = surface->dpi_scale(maximum_width.get(*state)); + if (text_width > mw && mw > 0) { text_width = mw; } + } + + /* if position isn't what expected, set fixed pos + * total_updates avoids setting fixed_pos when window + * is set to weird locations when started */ + /* // this is broken + if (total_updates >= 2 && !fixed_pos + && (window.x != ev.xconfigure.x + || window.y != ev.xconfigure.y) + && (ev.xconfigure.x != 0 + || ev.xconfigure.y != 0)) { + fixed_pos = 1; + } */ + } + + return true; +} + +EV_HANDLER(border_crossing) { + if (ev.type != EnterNotify && ev.type != LeaveNotify) return false; + if (window.xi_opcode != 0) return true; // handled by mouse_input already + + bool not_over_conky = ev.xcrossing.x_root <= window.x || + ev.xcrossing.y_root <= window.y || + ev.xcrossing.x_root >= window.x + window.width || + ev.xcrossing.y_root >= window.y + window.height; + + if ((not_over_conky && ev.xcrossing.type == LeaveNotify) || + (!not_over_conky && ev.xcrossing.type == EnterNotify)) { + llua_mouse_hook(mouse_crossing_event( + ev.xcrossing.type == EnterNotify ? mouse_event_t::AREA_ENTER + : mouse_event_t::AREA_LEAVE, + ev.xcrossing.x, ev.xcrossing.y, ev.xcrossing.x_root, + ev.xcrossing.y_root)); + } + return true; +} + #ifdef BUILD_XDAMAGE - if (ev.type == x11_stuff.event_base + XDamageNotify) { - auto *dev = reinterpret_cast(&ev); +EV_HANDLER(damage) { + if (ev.type != x11_stuff.event_base + XDamageNotify) return false; - XFixesSetRegion(display, x11_stuff.part, &dev->area, 1); - XFixesUnionRegion(display, x11_stuff.region2, x11_stuff.region2, - x11_stuff.part); - continue; // TODO: Propagate damage - } + auto *dev = reinterpret_cast(&ev); + + XFixesSetRegion(display, x11_stuff.part, &dev->area, 1); + XFixesUnionRegion(display, x11_stuff.region2, x11_stuff.region2, + x11_stuff.part); + return true; +} +#else +EV_HANDLER(damage) { return false; } #endif /* BUILD_XDAMAGE */ - break; - } + +/// Handles all events conky can receive. +/// +/// @return true if event should move input focus to conky +bool process_event(conky::display_output_x11 *surface, Display *display, + XEvent ev, bool *consumed, bool *focus) { +#define HANDLE_EV(handler) \ + if (_conky_ev_handle_##handler(surface, display, ev, consumed, focus)) \ + return true + + HANDLE_EV(mouse_input); + HANDLE_EV(property_notify); + + // only accept remaining events if they're sent to Conky. + if (ev.xany.window != window.window) return; + + HANDLE_EV(expose); + HANDLE_EV(reparent); + HANDLE_EV(border_crossing); + HANDLE_EV(damage); + + // event not handled + + // *consumed = false; // old behavior + // we don't want to forward majoriy of events though + + return false; +} + +void process_surface_events(conky::display_output_x11 *surface, + Display *display) { + int pending = XPending(display); + if (pending == 0) return; + + DBGP2("Processing %d X11 events...", pending); + + /* handle X events */ + while (XPending(display) != 0) { + XEvent ev; + XNextEvent(display, &ev); + + /* + indicates whether processed event was consumed; true by default so we don't + propagate handled events unless they explicitly state they haven't been + consumed. + */ + bool consumed = true; + bool focus = false; + process_event(surface, display, ev, &consumed, &focus); if (!consumed) { propagate_x11_event(ev); - } else { - InputEvent *i_ev = xev_as_input_event(ev); - if (i_ev != nullptr) { - XSetInputFocus(display, window.window, RevertToParent, - i_ev->common.time); - } + } else if (focus) { + XSetInputFocus(display, window.window, RevertToParent, CurrentTime); } } - DBGP2("Done with events!"); + + DBGP2("Done processing %d events.", pending); } void display_output_x11::sigterm_cleanup() { diff --git a/src/display-x11.hh b/src/display-x11.hh index 4f4eb4d33..e751f7949 100644 --- a/src/display-x11.hh +++ b/src/display-x11.hh @@ -36,7 +36,6 @@ namespace conky { * A base class for X11 display output. */ class display_output_x11 : public display_output_base { - void process_surface_events(Display *); public: explicit display_output_x11();