1
0
mirror of https://github.com/Llewellynvdm/conky.git synced 2025-01-16 12:10:31 +00:00

Exclusive XInput event handling (#1821)

- This commit changes mouse events to use XInput exclusively if it's not disabled.
- Added support for changing valuator properties through device configuration (see wiki for details).
- Fixed and cleaned up a lot of previously added code.
This commit is contained in:
Tin Švagelj 2024-04-17 02:04:14 +00:00 committed by GitHub
parent d56c0372f5
commit f4b3229fc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 945 additions and 292 deletions

View File

@ -53,7 +53,9 @@
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>
#include <map>
#include <sstream> #include <sstream>
#include <tuple>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -442,72 +444,156 @@ bool handle_event(conky::display_output_x11 *surface, Display *display,
} }
#ifdef OWN_WINDOW #ifdef OWN_WINDOW
#ifdef BUILD_XINPUT
template <> template <>
bool handle_event<x_event_handler::XINPUT_MOTION>( bool handle_event<x_event_handler::MOUSE_INPUT>(
conky::display_output_x11 *surface, Display *display, XEvent &ev, conky::display_output_x11 *surface, Display *display, XEvent &ev,
bool *consumed, void **cookie) { bool *consumed, void **cookie) {
if (ev.type != GenericEvent || ev.xcookie.extension != window.xi_opcode) #ifdef BUILD_XINPUT
if (ev.type == ButtonPress || ev.type == ButtonRelease ||
ev.type == MotionNotify) {
// destroy basic X11 events; and manufacture them later when trying to
// propagate XInput ones - this is required because there's no (simple) way
// of making sure the lua hook controls both when it only handles XInput
// ones.
*consumed = true;
return true;
}
if (ev.type != GenericEvent || ev.xgeneric.extension != window.xi_opcode)
return false; return false;
if (!XGetEventData(display, &ev.xcookie)) { if (!XGetEventData(display, &ev.xcookie)) {
NORM_ERR("unable to get XInput event data"); // already consumed
return false; return true;
}
xi_event_type event_type = ev.xcookie.evtype;
if (event_type == XI_HierarchyChanged) {
auto device_change = reinterpret_cast<XIHierarchyEvent *>(ev.xcookie.data);
handle_xi_device_change(device_change);
XFreeEventData(display, &ev.xcookie);
return true;
} }
auto *data = reinterpret_cast<XIDeviceEvent *>(ev.xcookie.data); auto *data = xi_event_data::read_cookie(display, ev.xcookie.data);
XFreeEventData(display, &ev.xcookie);
// the only way to differentiate between a scroll and move event is if (data == nullptr) {
// though valuators - move has first 2 set, other axis movements have // we ate the cookie, Xi event not handled
// other. return true;
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;
}
} }
*cookie = data;
if (data->evtype == XI_Motion && is_cursor_move) { Window event_window =
Window query_result =
query_x11_window_at_pos(display, data->root_x, data->root_y); query_x11_window_at_pos(display, data->root_x, data->root_y);
// query_result is not window.window in some cases. // query_result is not window.window in some cases.
query_result = query_x11_last_descendant(display, query_result); modifier_state_t mods = x11_modifier_state(data->mods.effective);
static bool cursor_inside = false; bool same_window = query_x11_top_parent(display, event_window) ==
query_x11_top_parent(display, window.window);
// - over conky window bool cursor_over_conky = same_window && data->root_x >= window.x &&
// - 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_x < (window.x + window.width) &&
data->root_y >= window.y && data->root_y >= window.y &&
data->root_y < (window.y + window.height))); data->root_y < (window.y + window.height);
// XInput reports events twice on some hardware (even by 'xinput --test-xi2')
auto hash = std::make_tuple(data->serial, data->evtype, data->event);
typedef std::map<decltype(hash), Time> MouseEventDebounceMap;
static MouseEventDebounceMap debounce{};
Time now = data->time;
bool already_handled = debounce.count(hash) > 0;
debounce[hash] = now;
// clear stale entries
for (auto iter = debounce.begin(); iter != debounce.end();) {
if (data->time - iter->second > 1000) {
iter = debounce.erase(iter);
} else {
++iter;
}
}
if (already_handled) {
*consumed = true;
return true;
}
if (data->evtype == XI_Motion) {
// TODO: Make valuator_index names configurable?
bool is_move = data->test_valuator(valuator_t::MOVE_X) ||
data->test_valuator(valuator_t::MOVE_Y);
bool is_scroll = data->test_valuator(valuator_t::SCROLL_X) ||
data->test_valuator(valuator_t::SCROLL_Y);
if (is_move) {
static bool cursor_inside = false;
// generate crossing events
if (cursor_over_conky) { if (cursor_over_conky) {
if (!cursor_inside) { if (!cursor_inside) {
llua_mouse_hook(mouse_crossing_event( *consumed = llua_mouse_hook(mouse_crossing_event(
mouse_event_t::AREA_ENTER, data->root_x - window.x, mouse_event_t::AREA_ENTER, data->root_x - window.x,
data->root_y - window.x, data->root_x, data->root_y)); data->root_y - window.x, data->root_x, data->root_y));
} }
cursor_inside = true; cursor_inside = true;
} else if (cursor_inside) { } else if (cursor_inside) {
llua_mouse_hook(mouse_crossing_event( *consumed = llua_mouse_hook(mouse_crossing_event(
mouse_event_t::AREA_LEAVE, data->root_x - window.x, mouse_event_t::AREA_LEAVE, data->root_x - window.x,
data->root_y - window.x, data->root_x, data->root_y)); data->root_y - window.x, data->root_x, data->root_y));
cursor_inside = false; cursor_inside = false;
} }
// generate movement events
if (cursor_over_conky) {
*consumed = llua_mouse_hook(mouse_move_event(
data->event_x, data->event_y, data->root_x, data->root_y, mods));
} }
XFreeEventData(display, &ev.xcookie); }
if (is_scroll && cursor_over_conky) {
scroll_direction_t scroll_direction;
auto vertical = data->valuator_relative_value(valuator_t::SCROLL_Y);
double vertical_value = vertical.value_or(0.0);
if (vertical_value != 0.0) {
scroll_direction = vertical_value < 0.0
? scroll_direction_t::SCROLL_UP
: scroll_direction_t::SCROLL_DOWN;
} else {
auto horizontal = data->valuator_relative_value(valuator_t::SCROLL_X);
double horizontal_value = horizontal.value_or(0.0);
if (horizontal_value != 0.0) {
scroll_direction = horizontal_value < 0.0
? scroll_direction_t::SCROLL_LEFT
: scroll_direction_t::SCROLL_RIGHT;
}
}
if (scroll_direction != scroll_direction_t::SCROLL_UNKNOWN) {
*consumed = llua_mouse_hook(
mouse_scroll_event(data->event_x, data->event_y, data->root_x,
data->root_y, scroll_direction, mods));
}
}
} else if (cursor_over_conky && (data->evtype == XI_ButtonPress ||
data->evtype == XI_ButtonRelease)) {
if (data->detail >= 4 && data->detail <= 7) {
// Handled via motion event valuators, ignoring "backward compatibility"
// ones.
return true; return true;
} }
#endif /* BUILD_XINPUT */
template <> mouse_event_t type = mouse_event_t::MOUSE_PRESS;
bool handle_event<x_event_handler::MOUSE_INPUT>( if (data->evtype == XI_ButtonRelease) {
conky::display_output_x11 *surface, Display *display, XEvent &ev, type = mouse_event_t::MOUSE_RELEASE;
bool *consumed, void **cookie) { }
mouse_button_t button = x11_mouse_button_code(data->detail);
*consumed = llua_mouse_hook(mouse_button_event(type, data->event_x,
data->event_y, data->root_x,
data->root_y, button, mods));
}
#else /* BUILD_XINPUT */
if (ev.type != ButtonPress && ev.type != ButtonRelease && if (ev.type != ButtonPress && ev.type != ButtonRelease &&
ev.type != MotionNotify) ev.type != MotionNotify)
return false; return false;
@ -551,7 +637,7 @@ bool handle_event<x_event_handler::MOUSE_INPUT>(
// always propagate mouse input if not handling mouse events // always propagate mouse input if not handling mouse events
*consumed = false; *consumed = false;
#endif /* BUILD_MOUSE_EVENTS */ #endif /* BUILD_MOUSE_EVENTS */
#endif /* BUILD_XINPUT */
if (!own_window.get(*state)) return true; if (!own_window.get(*state)) return true;
switch (own_window_type.get(*state)) { switch (own_window_type.get(*state)) {
case window_type::TYPE_NORMAL: case window_type::TYPE_NORMAL:
@ -751,9 +837,9 @@ void process_surface_events(conky::display_output_x11 *surface,
XNextEvent(display, &ev); XNextEvent(display, &ev);
/* /*
indicates whether processed event was consumed; true by default so we don't indicates whether processed event was consumed; true by default so we
propagate handled events unless they explicitly state they haven't been don't propagate handled events unless they explicitly state they haven't
consumed. been consumed.
*/ */
bool consumed = true; bool consumed = true;
void *cookie = nullptr; void *cookie = nullptr;

View File

@ -27,8 +27,20 @@
#include "logging.h" #include "logging.h"
#ifdef BUILD_XINPUT
#include <cstring>
#endif
extern "C" { extern "C" {
#include <lua.h> #include <lua.h>
#ifdef BUILD_XINPUT
#include <X11/extensions/XInput2.h>
#endif
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
} }
namespace conky { namespace conky {
@ -206,4 +218,416 @@ void mouse_button_event::push_lua_data(lua_State *L) const {
push_mods(L, this->mods); push_mods(L, this->mods);
} }
#ifdef BUILD_XINPUT
/// Last global device id.
size_t last_device_id = 0;
static std::map<size_t, device_info> device_info_cache{};
static std::map<xi_device_id, size_t> xi_id_mapping{};
device_info *device_info::from_xi_id(xi_device_id device_id, Display *display) {
if (xi_id_mapping.count(device_id) > 0) {
return &device_info_cache[xi_id_mapping[device_id]];
}
if (display == nullptr) return nullptr;
int num_devices;
XIDeviceInfo *device = XIQueryDevice(display, device_id, &num_devices);
if (num_devices == 0) return nullptr;
device_info info =
device_info{.id = device_id, .name = std::string(device->name)};
size_t id = last_device_id++;
info.init_xi_device(display, device);
XIFreeDeviceInfo(device);
device_info_cache[id] = info;
xi_id_mapping[device_id] = id;
return &device_info_cache[id];
}
void handle_xi_device_change(const XIHierarchyEvent *event) {
if ((event->flags & XISlaveRemoved) != 0) {
for (int i = 0; i < event->num_info; i++) {
auto info = event->info[i];
if ((info.flags & XISlaveRemoved) == 0) continue;
if (xi_id_mapping.count(info.deviceid) == 0) continue;
size_t id = xi_id_mapping[info.deviceid];
xi_id_mapping.erase(info.deviceid);
device_info_cache.erase(id);
}
}
if ((event->flags & XISlaveAdded) != 0) {
for (int i = 0; i < event->num_info; i++) {
auto info = event->info[i];
if ((info.flags & XISlaveAdded) == 0) continue;
if (info.use == IsXPointer || info.use == IsXExtensionPointer)
conky::device_info::from_xi_id(info.deviceid, event->display);
}
}
}
/// Allows override of valuator indices in `xorg.conf` in case they're wrong for
/// some device (unlikely).
size_t fixed_valuator_index(Display *display, XIDeviceInfo *device,
valuator_t valuator) {
const std::array<const char *, valuator_t::VALUATOR_COUNT> atom_names = {
"ConkyValuatorMoveX", "ConkyValuatorMoveY", "ConkyValuatorScrollX",
"ConkyValuatorScrollY"};
Atom override_atom = XInternAtom(display, atom_names[valuator], False);
unsigned char *value;
Atom type_return;
int format_return;
unsigned long num_items;
unsigned long bytes_after;
do {
if (XIGetProperty(display, device->deviceid, override_atom, 0, 1, False,
XA_INTEGER, &type_return, &format_return, &num_items,
&bytes_after,
reinterpret_cast<unsigned char **>(&value)) == Success) {
if (num_items == 0) break;
if (type_return != XA_INTEGER) {
NORM_ERR(
"invalid '%s' option value, expected a single integer; value will "
"be ignored",
atom_names[valuator]);
XFree(value);
break;
}
uint32_t result = *reinterpret_cast<uint32_t *>(value);
XFree(value);
return static_cast<size_t>(result);
}
} while (true);
return valuator;
}
/// Allows override of valuator value type in `xorg.conf` in case they're wrong
/// for some device (happens with VMs and some devices/setups).
bool fixed_valuator_relative(Display *display, XIDeviceInfo *device,
valuator_t valuator,
XIValuatorClassInfo *class_info) {
const std::array<const char *, 2> atom_names = {
"ConkyValuatorMoveMode",
"ConkyValuatorScrollMode",
};
Atom override_atom = XInternAtom(display, atom_names[valuator >> 1], True);
unsigned char *value_return;
Atom type_return;
int format_return;
unsigned long num_items;
unsigned long bytes_after;
do {
if (XIGetProperty(
display, device->deviceid, override_atom, 0, 9, False, XA_ATOM,
&type_return, &format_return, &num_items, &bytes_after,
reinterpret_cast<unsigned char **>(&value_return)) == Success) {
if (num_items == 0) break;
if (type_return != XA_ATOM) {
NORM_ERR(
"invalid '%s' option value, expected an atom (string); value will "
"be ignored",
atom_names[valuator >> 1]);
XFree(value_return);
break;
}
Atom return_atom = *reinterpret_cast<Atom *>(value_return);
XFree(value_return);
char *value = XGetAtomName(display, return_atom);
// lowercase value
for (auto c = value; *c; ++c) *c = tolower(*c);
bool relative = false;
if (strcmp(reinterpret_cast<char *>(value), "relative") == 0) {
relative = true;
} else if (strcmp(reinterpret_cast<char *>(value), "absolute") != 0) {
NORM_ERR(
"unknown '%s' option value: '%s', expected 'absolute' or "
"'relative'; "
"value will be ignored",
atom_names[valuator >> 1]);
XFree(value);
break;
}
XFree(value);
return relative;
}
} while (true);
return class_info->mode == XIModeRelative;
}
void device_info::init_xi_device(
Display *display, std::variant<xi_device_id, XIDeviceInfo *> source) {
XIDeviceInfo *device = nullptr;
if (std::holds_alternative<XIDeviceInfo *>(source)) {
device = std::get<XIDeviceInfo *>(source);
} else if (std::holds_alternative<xi_device_id>(source)) {
int num_devices;
device =
XIQueryDevice(display, std::get<xi_device_id>(source), &num_devices);
if (num_devices == 0) return;
}
if (device == nullptr) return;
std::array<size_t, valuator_t::VALUATOR_COUNT> valuator_indices;
for (size_t i = 0; i < valuator_t::VALUATOR_COUNT; i++) {
valuator_indices[i] =
fixed_valuator_index(display, device, static_cast<valuator_t>(i));
}
// class order is undefined!
for (int i = 0; i < device->num_classes; i++) {
if (device->classes[i]->type != XIValuatorClass) continue;
XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)device->classes[i];
// check if one of used (mapped) valuators
valuator_t valuator = valuator_t::VALUATOR_COUNT;
for (size_t i = 0; i < valuator_t::VALUATOR_COUNT; i++) {
if (valuator_indices[i] == class_info->number) {
valuator = static_cast<valuator_t>(i);
break;
}
}
if (valuator == valuator_t::VALUATOR_COUNT) { continue; }
auto info = conky_valuator_info{
.index = static_cast<size_t>(class_info->number),
.min = class_info->min,
.max = class_info->max,
.value = class_info->value,
.relative =
fixed_valuator_relative(display, device, valuator, class_info),
};
this->valuators[valuator] = info;
}
if (std::holds_alternative<xi_device_id>(source)) {
XIFreeDeviceInfo(device);
}
}
conky_valuator_info &device_info::valuator(valuator_t valuator) {
return this->valuators[valuator];
}
xi_event_data *xi_event_data::read_cookie(Display *display, const void *data) {
const XIDeviceEvent *source = reinterpret_cast<const XIDeviceEvent *>(data);
xi_event_type event_type = source->evtype;
if (!(event_type == XI_Motion || event_type == XI_ButtonPress ||
event_type == XI_ButtonRelease)) {
return nullptr;
}
std::bitset<32> buttons;
for (size_t bi = 1; bi < source->buttons.mask_len * 8; bi++) {
if (XIMaskIsSet(source->buttons.mask, bi)) buttons[bi] = true;
}
std::map<size_t, double> valuators{};
double *values = source->valuators.values;
for (size_t vi = 0; vi < source->valuators.mask_len * 8; vi++) {
if (XIMaskIsSet(source->valuators.mask, vi)) valuators[vi] = *values++;
}
auto device = device_info::from_xi_id(source->deviceid, source->display);
if (device == nullptr) return nullptr; // shouldn't happen
auto result = new xi_event_data{
.evtype = static_cast<xi_event_type>(source->evtype),
.serial = source->serial,
.send_event = source->send_event,
.display = source->display,
.extension = source->extension,
.time = source->time,
.device = device,
.sourceid = source->sourceid,
.detail = source->detail,
.root = source->root,
.event = source->event,
.child = source->child,
.root_x = source->root_x,
.root_y = source->root_y,
.event_x = source->event_x,
.event_y = source->event_y,
.flags = source->flags,
.buttons = buttons,
.valuators = valuators,
.mods = source->mods,
.group = source->group,
.valuators_relative = {0.0, 0.0, 0.0, 0.0},
};
for (size_t v = 0; v < valuator_t::VALUATOR_COUNT; v++) {
valuator_t valuator = static_cast<valuator_t>(v);
auto &valuator_info = device->valuator(valuator);
if (result->valuators.count(valuator_info.index) == 0) { continue; }
auto current = result->valuators[valuator_info.index];
if (valuator_info.relative) {
result->valuators_relative[v] = current;
} else {
// XXX these doubles come from int values and might wrap around though
// it's hard to tell what int type is the source as it depends on the
// device/driver.
result->valuators_relative[v] = current - valuator_info.value;
}
valuator_info.value = current;
}
return result;
}
bool xi_event_data::test_valuator(valuator_t valuator) const {
return this->valuators.count(this->device->valuator(valuator).index) > 0;
}
conky_valuator_info *xi_event_data::valuator_info(valuator_t valuator) const {
return &this->device->valuator(valuator);
}
std::optional<double> xi_event_data::valuator_value(valuator_t valuator) const {
auto info = this->valuator_info(valuator);
if (info == nullptr) return std::nullopt;
size_t index = info->index;
if (this->valuators.count(index) == 0) return std::nullopt;
return this->valuators.at(index);
}
std::optional<double> xi_event_data::valuator_relative_value(
valuator_t valuator) const {
return this->valuators_relative.at(valuator);
}
std::vector<std::tuple<int, XEvent *>> xi_event_data::generate_events(
Window target, Window child, double target_x, double target_y) const {
std::vector<std::tuple<int, XEvent *>> result{};
if (this->evtype == XI_Motion) {
auto device_info = this->device;
bool is_move = this->test_valuator(valuator_t::MOVE_X) ||
this->test_valuator(valuator_t::MOVE_Y);
bool is_scroll = this->test_valuator(valuator_t::SCROLL_X) ||
this->test_valuator(valuator_t::SCROLL_Y);
if (is_move) {
XEvent *produced = new XEvent;
std::memset(produced, 0, sizeof(XEvent));
XMotionEvent *e = &produced->xmotion;
e->type = MotionNotify;
e->display = this->display;
e->root = this->root;
e->window = target;
e->subwindow = child;
e->time = CurrentTime;
e->x = static_cast<int>(target_x);
e->y = static_cast<int>(target_y);
e->x_root = static_cast<int>(this->root_x);
e->y_root = static_cast<int>(this->root_y);
e->state = this->mods.effective;
e->is_hint = NotifyNormal;
e->same_screen = True;
result.emplace_back(std::make_tuple(PointerMotionMask, produced));
}
if (is_scroll) {
XEvent *produced = new XEvent;
std::memset(produced, 0, sizeof(XEvent));
uint scroll_direction = 4;
auto vertical = this->valuator_relative_value(valuator_t::SCROLL_Y);
double vertical_value = vertical.value_or(0.0);
if (vertical_value != 0.0) {
scroll_direction = vertical_value < 0.0 ? Button4 : Button5;
} else {
auto horizontal = this->valuator_relative_value(valuator_t::SCROLL_X);
double horizontal_value = horizontal.value_or(0.0);
if (horizontal_value != 0.0) {
scroll_direction = horizontal_value < 0.0 ? 6 : 7;
}
}
XButtonEvent *e = &produced->xbutton;
e->display = display;
e->root = this->root;
e->window = target;
e->subwindow = child;
e->time = CurrentTime;
e->x = static_cast<int>(target_x);
e->y = static_cast<int>(target_y);
e->x_root = static_cast<int>(this->root_x);
e->y_root = static_cast<int>(this->root_y);
e->state = this->mods.effective;
e->button = scroll_direction;
e->same_screen = True;
XEvent *press = new XEvent;
e->type = ButtonPress;
std::memcpy(press, produced, sizeof(XEvent));
result.emplace_back(std::make_tuple(ButtonPressMask, press));
e->type = ButtonRelease;
result.emplace_back(std::make_tuple(ButtonReleaseMask, produced));
}
} else {
XEvent *produced = new XEvent;
std::memset(produced, 0, sizeof(XEvent));
XButtonEvent *e = &produced->xbutton;
e->display = display;
e->root = this->root;
e->window = target;
e->subwindow = child;
e->time = CurrentTime;
e->x = static_cast<int>(target_x);
e->y = static_cast<int>(target_y);
e->x_root = static_cast<int>(this->root_x);
e->y_root = static_cast<int>(this->root_y);
e->state = this->mods.effective;
e->button = this->detail;
e->same_screen = True;
long event_mask = NoEventMask;
switch (this->evtype) {
case XI_ButtonPress:
e->type = ButtonPress;
event_mask = ButtonPressMask;
break;
case XI_ButtonRelease:
e->type = ButtonRelease;
event_mask = ButtonReleaseMask;
switch (this->detail) {
case 1:
event_mask |= Button1MotionMask;
break;
case 2:
event_mask |= Button2MotionMask;
break;
case 3:
event_mask |= Button3MotionMask;
break;
case 4:
event_mask |= Button4MotionMask;
break;
case 5:
event_mask |= Button5MotionMask;
break;
}
break;
}
result.emplace_back(std::make_tuple(event_mask, produced));
}
return result;
}
#endif /* BUILD_XINPUT */
} // namespace conky } // namespace conky

View File

@ -28,9 +28,25 @@
#include "config.h" #include "config.h"
#include "logging.h" #include "logging.h"
#ifdef BUILD_XINPUT
#include <array>
#include <map>
#include <optional>
#include <tuple>
#include <variant>
#include <vector>
#endif /* BUILD_XINPUT */
extern "C" { extern "C" {
#ifdef BUILD_X11 #ifdef BUILD_X11
#include <X11/X.h> #include <X11/X.h>
#ifdef BUILD_XINPUT
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#undef COUNT // define from X11/extendsions/Xi.h
#endif /* BUILD_XINPUT */
#endif /* BUILD_X11 */ #endif /* BUILD_X11 */
#include <lua.h> #include <lua.h>
@ -41,7 +57,7 @@ extern "C" {
#include <dev/evdev/input-event-codes.h> #include <dev/evdev/input-event-codes.h>
#elif __DragonFly__ #elif __DragonFly__
#include <dev/misc/evdev/input-event-codes.h> #include <dev/misc/evdev/input-event-codes.h>
#else #else /* platform */
// Probably incorrect for some platforms, feel free to add your platform to the // Probably incorrect for some platforms, feel free to add your platform to the
// above list if it has other event codes or a standard file containing them. // above list if it has other event codes or a standard file containing them.
@ -56,7 +72,7 @@ extern "C" {
#define BTN_BACK 0x116 #define BTN_BACK 0x116
// Forward mouse button event code // Forward mouse button event code
#define BTN_FORWARD 0x115 #define BTN_FORWARD 0x115
#endif #endif /* platform */
} }
namespace conky { namespace conky {
@ -227,6 +243,84 @@ struct mouse_crossing_event : public mouse_positioned_event {
: mouse_positioned_event{type, x, y, x_abs, y_abs} {}; : mouse_positioned_event{type, x, y, x_abs, y_abs} {};
}; };
#ifdef BUILD_XINPUT
typedef int xi_device_id;
typedef int xi_event_type;
enum valuator_t : size_t { MOVE_X, MOVE_Y, SCROLL_X, SCROLL_Y, VALUATOR_COUNT };
struct conky_valuator_info {
size_t index;
double min;
double max;
double value;
bool relative;
};
struct device_info {
/// @brief Device name.
xi_device_id id;
std::string name;
std::array<conky_valuator_info, valuator_t::VALUATOR_COUNT> valuators{};
static device_info *from_xi_id(xi_device_id id, Display *display = nullptr);
conky_valuator_info &valuator(valuator_t valuator);
private:
void init_xi_device(Display *display,
std::variant<xi_device_id, XIDeviceInfo *> device);
};
void handle_xi_device_change(const XIHierarchyEvent *event);
/// Almost an exact copy of `XIDeviceEvent`, except it owns all data.
struct xi_event_data {
xi_event_type evtype;
unsigned long serial;
Bool send_event;
Display *display;
/// XI extension offset
// TODO: Check whether this is consistent between different clients by
// printing.
int extension;
Time time;
device_info *device;
int sourceid;
int detail;
Window root;
Window event;
Window child;
double root_x;
double root_y;
double event_x;
double event_y;
int flags;
/// pressed button mask
std::bitset<32> buttons;
std::map<size_t, double> valuators;
XIModifierState mods;
XIGroupState group;
// Extra data
/// Precomputed relative values
std::array<double, valuator_t::VALUATOR_COUNT> valuators_relative;
static xi_event_data *read_cookie(Display *display, const void *data);
bool test_valuator(valuator_t id) const;
conky_valuator_info *valuator_info(valuator_t id) const;
std::optional<double> valuator_value(valuator_t id) const;
std::optional<double> valuator_relative_value(valuator_t valuator) const;
std::vector<std::tuple<int, XEvent *>> generate_events(Window target,
Window child,
double target_x,
double target_y) const;
};
#endif /* BUILD_XINPUT */
} // namespace conky } // namespace conky
#endif /* MOUSE_EVENTS_H */ #endif /* MOUSE_EVENTS_H */

View File

@ -37,6 +37,12 @@
#include "gui.h" #include "gui.h"
#include "logging.h" #include "logging.h"
#ifdef BUILD_XINPUT
#include "mouse-events.h"
#include <vector>
#endif
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
@ -44,6 +50,7 @@
#include <cstring> #include <cstring>
#include <string> #include <string>
extern "C" {
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wvariadic-macros" #pragma GCC diagnostic ignored "-Wvariadic-macros"
#pragma GCC diagnostic ignored "-Wregister" #pragma GCC diagnostic ignored "-Wregister"
@ -73,13 +80,14 @@
#include <X11/extensions/Xfixes.h> #include <X11/extensions/Xfixes.h>
#endif /* BUILD_XFIXES */ #endif /* BUILD_XFIXES */
#ifdef BUILD_XINPUT #ifdef BUILD_XINPUT
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h> #include <X11/extensions/XInput2.h>
#include <vector>
#endif /* BUILD_XINPUT */ #endif /* BUILD_XINPUT */
#ifdef HAVE_XCB_ERRORS #ifdef HAVE_XCB_ERRORS
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xcb_errors.h> #include <xcb/xcb_errors.h>
#endif #endif
}
/* some basic X11 stuff */ /* some basic X11 stuff */
Display *display = nullptr; Display *display = nullptr;
@ -307,6 +315,52 @@ __attribute__((noreturn)) static int x11_ioerror_handler(Display *d) {
CRIT_ERR("X IO Error: Display %lx\n", reinterpret_cast<uint64_t>(d)); CRIT_ERR("X IO Error: Display %lx\n", reinterpret_cast<uint64_t>(d));
} }
/// @brief Function to get virtual root windows of screen.
///
/// Some WMs (swm, tvtwm, amiwm, enlightenment, etc.) use virtual roots to
/// manage workspaces. These are direct descendants of root and WMs reparent all
/// children to them.
///
/// @param screen screen to get the (current) virtual root of @return the
/// virtual root window of the screen
static Window VRootWindowOfScreen(Screen *screen) {
Window root = screen->root;
Display *dpy = screen->display;
Window rootReturn, parentReturn, *children;
unsigned int numChildren;
Atom actual_type;
int actual_format;
unsigned long nitems, bytesafter;
/* go look for a virtual root */
Atom __SWM_VROOT = ATOM(__SWM_VROOT);
if (XQueryTree(dpy, root, &rootReturn, &parentReturn, &children,
&numChildren)) {
for (int i = 0; i < numChildren; i++) {
Window *newRoot = None;
if (XGetWindowProperty(
dpy, children[i], __SWM_VROOT, 0, 1, False, XA_WINDOW,
&actual_type, &actual_format, &nitems, &bytesafter,
reinterpret_cast<unsigned char **>(&newRoot)) == Success &&
newRoot != None) {
root = *newRoot;
break;
}
}
if (children) XFree((char *)children);
}
return root;
}
inline Window VRootWindow(Display *display, int screen) {
return VRootWindowOfScreen(ScreenOfDisplay(display, screen));
}
inline Window DefaultVRootWindow(Display *display) {
return VRootWindowOfScreen(DefaultScreenOfDisplay(display));
}
/* X11 initializer */ /* X11 initializer */
static void init_x11() { static void init_x11() {
DBGP("enter init_x11()"); DBGP("enter init_x11()");
@ -416,75 +470,21 @@ static void update_workarea() {
* Return desktop window on success, * Return desktop window on success,
* and set root and desktop byref return values. * and set root and desktop byref return values.
* Return 0 on failure. */ * Return 0 on failure. */
static Window find_desktop_window(Window *p_root, Window *p_desktop) { static Window find_desktop_window(Window root) {
Atom type; Window desktop = root;
int format, i;
unsigned long nitems, bytes;
unsigned int n;
if (!display) return 0;
Window root = RootWindow(display, screen);
Window win;
Window troot, parent, *children;
unsigned char *buf = nullptr;
if ((p_root == nullptr) || (p_desktop == nullptr)) { return 0; }
/* some window managers set __SWM_VROOT to some child of root window */
XQueryTree(display, root, &troot, &parent, &children, &n);
for (i = 0; i < static_cast<int>(n); i++) {
if (XGetWindowProperty(display, children[i], ATOM(__SWM_VROOT), 0, 1, False,
XA_WINDOW, &type, &format, &nitems, &bytes,
&buf) == Success &&
type == XA_WINDOW) {
win = *reinterpret_cast<Window *>(buf);
XFree(buf);
XFree(children);
fprintf(stderr,
PACKAGE_NAME
": desktop window (%lx) found from __SWM_VROOT property\n",
win);
fflush(stderr);
*p_root = win;
*p_desktop = win;
return win;
}
if (buf != nullptr) {
XFree(buf);
buf = nullptr;
}
}
XFree(children);
/* get subwindows from root */ /* get subwindows from root */
win = find_subwindow(root, -1, -1); desktop = find_subwindow(root, -1, -1);
update_workarea(); update_workarea();
desktop = find_subwindow(desktop, workarea[2], workarea[3]);
win = find_subwindow(win, workarea[2], workarea[3]); if (desktop != root) {
DBGP2("desktop window (0x%lx) is subwindow of root window (0x%lx)", desktop,
if (buf != nullptr) { root);
XFree(buf);
buf = nullptr;
}
if (win != root) {
fprintf(stderr,
PACKAGE_NAME
": desktop window (%lx) is subwindow of root window (%lx)\n",
win, root);
} else { } else {
fprintf(stderr, PACKAGE_NAME ": desktop window (%lx) is root window\n", DBGP2("desktop window (0x%lx) is root window", desktop);
win);
} }
return desktop;
fflush(stderr);
*p_root = root;
*p_desktop = win;
return win;
} }
#ifdef OWN_WINDOW #ifdef OWN_WINDOW
@ -579,18 +579,21 @@ void x11_init_window(lua::state &l, bool own) {
// own is unused if OWN_WINDOW is not defined // own is unused if OWN_WINDOW is not defined
(void)own; (void)own;
window.root = VRootWindow(display, screen);
if (window.root == None) {
DBGP2("no desktop window found");
return;
}
window.desktop = find_desktop_window(window.root);
window.visual = DefaultVisual(display, screen);
window.colourmap = DefaultColormap(display, screen);
#ifdef OWN_WINDOW #ifdef OWN_WINDOW
if (own) { if (own) {
int depth = 0, flags = CWOverrideRedirect | CWBackingStore; int depth = 0, flags = CWOverrideRedirect | CWBackingStore;
Visual *visual = nullptr; Visual *visual = nullptr;
if (find_desktop_window(&window.root, &window.desktop) == 0U) {
DBGP2("no desktop window found");
return;
}
window.visual = DefaultVisual(display, screen);
window.colourmap = DefaultColormap(display, screen);
depth = CopyFromParent; depth = CopyFromParent;
visual = CopyFromParent; visual = CopyFromParent;
#ifdef BUILD_ARGB #ifdef BUILD_ARGB
@ -909,16 +912,7 @@ void x11_init_window(lua::state &l, bool own) {
{ {
XWindowAttributes attrs; XWindowAttributes attrs;
if (window.window == 0u) { if (window.window == None) { window.window = window.desktop; }
window.window = find_desktop_window(&window.root, &window.desktop);
}
if (window.window == 0u) {
DBGP2("no root window found");
return;
}
window.visual = DefaultVisual(display, screen);
window.colourmap = DefaultColormap(display, screen);
if (XGetWindowAttributes(display, window.window, &attrs) != 0) { if (XGetWindowAttributes(display, window.window, &attrs) != 0) {
window.width = attrs.width; window.width = attrs.width;
@ -939,14 +933,10 @@ void x11_init_window(lua::state &l, bool own) {
input_mask |= StructureNotifyMask | ButtonPressMask | ButtonReleaseMask; input_mask |= StructureNotifyMask | ButtonPressMask | ButtonReleaseMask;
} }
#ifdef BUILD_MOUSE_EVENTS #ifdef BUILD_MOUSE_EVENTS
/* it's not recommended to add event masks to special windows in X; causes a
* crash */
if (own && own_window_type.get(l) != TYPE_DESKTOP) {
input_mask |= PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
}
bool xinput_ok = false; bool xinput_ok = false;
#ifdef BUILD_XINPUT #ifdef BUILD_XINPUT
do { // not loop // not a loop; substitutes goto with break - if checks fail
do {
int _ignored; // segfault if NULL int _ignored; // segfault if NULL
if (!XQueryExtension(display, "XInputExtension", &window.xi_opcode, if (!XQueryExtension(display, "XInputExtension", &window.xi_opcode,
&_ignored, &_ignored)) { &_ignored, &_ignored)) {
@ -955,8 +945,8 @@ void x11_init_window(lua::state &l, bool own) {
break; break;
} }
int32_t major = 2, minor = 0; int major = 2, minor = 0;
uint32_t retval = XIQueryVersion(display, &major, &minor); int retval = XIQueryVersion(display, &major, &minor);
if (retval != Success) { if (retval != Success) {
NORM_ERR("Error: XInput 2.0 is not supported!"); NORM_ERR("Error: XInput 2.0 is not supported!");
break; break;
@ -964,18 +954,49 @@ void x11_init_window(lua::state &l, bool own) {
const std::size_t mask_size = (XI_LASTEVENT + 7) / 8; const std::size_t mask_size = (XI_LASTEVENT + 7) / 8;
unsigned char mask_bytes[mask_size] = {0}; /* must be zeroed! */ unsigned char mask_bytes[mask_size] = {0}; /* must be zeroed! */
XISetMask(mask_bytes, XI_HierarchyChanged);
XISetMask(mask_bytes, XI_Motion); XISetMask(mask_bytes, XI_Motion);
// Capture click events for "override" window type
if (!own) {
XISetMask(mask_bytes, XI_ButtonPress);
XISetMask(mask_bytes, XI_ButtonRelease);
}
XIEventMask ev_masks[1]; XIEventMask ev_masks[1];
ev_masks[0].deviceid = XIAllDevices; ev_masks[0].deviceid = XIAllDevices;
ev_masks[0].mask_len = sizeof(mask_bytes); ev_masks[0].mask_len = sizeof(mask_bytes);
ev_masks[0].mask = mask_bytes; ev_masks[0].mask = mask_bytes;
XISelectEvents(display, window.root, ev_masks, 1); XISelectEvents(display, window.root, ev_masks, 1);
if (own) {
XIClearMask(mask_bytes, XI_Motion);
XISetMask(mask_bytes, XI_ButtonPress);
XISetMask(mask_bytes, XI_ButtonRelease);
ev_masks[0].deviceid = XIAllDevices;
ev_masks[0].mask_len = sizeof(mask_bytes);
ev_masks[0].mask = mask_bytes;
XISelectEvents(display, window.window, ev_masks, 1);
}
// setup cache
int num_devices;
XDeviceInfo *info = XListInputDevices(display, &num_devices);
for (int i = 0; i < num_devices; i++) {
if (info[i].use == IsXPointer || info[i].use == IsXExtensionPointer) {
conky::device_info::from_xi_id(info[i].id, display);
}
}
XFreeDeviceList(info);
xinput_ok = true; xinput_ok = true;
} while (false); } while (false);
#endif /* BUILD_XINPUT */ #endif /* BUILD_XINPUT */
// Fallback to basic X11 enter/leave events if xinput fails to init.
// It's not recommended to add event masks to special windows in X; causes a
// crash (thus own_window_type != TYPE_DESKTOP)
if (!xinput_ok && own && own_window_type.get(l) != TYPE_DESKTOP) { if (!xinput_ok && own && own_window_type.get(l) != TYPE_DESKTOP) {
input_mask |= EnterWindowMask | LeaveWindowMask; input_mask |= PointerMotionMask | EnterWindowMask | LeaveWindowMask;
} }
#endif /* BUILD_MOUSE_EVENTS */ #endif /* BUILD_MOUSE_EVENTS */
#endif /* OWN_WINDOW */ #endif /* OWN_WINDOW */
@ -1342,20 +1363,10 @@ void print_mouse_speed(struct text_object *obj, char *p,
snprintf(p, p_max_size, "%d%%", (110 - threshold)); snprintf(p, p_max_size, "%d%%", (110 - threshold));
} }
InputEvent *xev_as_input_event(XEvent &ev) {
if (ev.type == KeyPress || ev.type == KeyRelease || ev.type == ButtonPress ||
ev.type == ButtonRelease || ev.type == MotionNotify ||
ev.type == EnterNotify || ev.type == LeaveNotify) {
return reinterpret_cast<InputEvent *>(&ev);
} else {
return nullptr;
}
}
/// @brief Returns a mask for the event_type /// @brief Returns a mask for the event_type
/// @param event_type Xlib event type /// @param event_type Xlib event type
/// @return Xlib event mask /// @return Xlib event mask
int ev_to_mask(int event_type) { int ev_to_mask(int event_type, int button) {
switch (event_type) { switch (event_type) {
case KeyPress: case KeyPress:
return KeyPressMask; return KeyPressMask;
@ -1364,7 +1375,20 @@ int ev_to_mask(int event_type) {
case ButtonPress: case ButtonPress:
return ButtonPressMask; return ButtonPressMask;
case ButtonRelease: case ButtonRelease:
switch (button) {
case 1:
return ButtonReleaseMask | Button1MotionMask;
case 2:
return ButtonReleaseMask | Button2MotionMask;
case 3:
return ButtonReleaseMask | Button3MotionMask;
case 4:
return ButtonReleaseMask | Button4MotionMask;
case 5:
return ButtonReleaseMask | Button5MotionMask;
default:
return ButtonReleaseMask; return ButtonReleaseMask;
}
case EnterNotify: case EnterNotify:
return EnterWindowMask; return EnterWindowMask;
case LeaveNotify: case LeaveNotify:
@ -1376,138 +1400,182 @@ int ev_to_mask(int event_type) {
} }
} }
void propagate_x11_event(XEvent &ev, const void *cookie) { #ifdef BUILD_XINPUT
bool focus = ev.type == ButtonPress; void propagate_xinput_event(const conky::xi_event_data *ev) {
if (ev->evtype != XI_Motion && ev->evtype != XI_ButtonPress &&
InputEvent *i_ev = xev_as_input_event(ev); ev->evtype != XI_ButtonRelease) {
if (i_ev == nullptr) {
// Not a known input event; blindly propagating them causes loops and all
// sorts of other evil.
return; return;
} }
i_ev->common.window = window.desktop; Window target = window.root;
i_ev->common.x = i_ev->common.x_root; Window child = None;
i_ev->common.y = i_ev->common.y_root; int target_x = ev->event_x;
i_ev->common.time = CurrentTime; int target_y = ev->event_y;
/* forward the event to the window below conky (e.g. caja) or desktop */
{ {
std::vector<Window> below = query_x11_windows_at_pos( std::vector<Window> below = query_x11_windows_at_pos(
display, i_ev->common.x_root, i_ev->common.y_root, display, ev->root_x, ev->root_y,
[](XWindowAttributes &a) { return a.map_state == IsViewable; }); [](XWindowAttributes &a) { return a.map_state == IsViewable; });
auto it = std::remove_if(below.begin(), below.end(), auto it = std::remove_if(below.begin(), below.end(),
[](Window w) { return w == window.window; }); [](Window w) { return w == window.window; });
below.erase(it, below.end()); below.erase(it, below.end());
if (!below.empty()) { if (!below.empty()) {
i_ev->common.window = below.back(); target = below.back();
// Update event x and y coordinates to be target window relative
XTranslateCoordinates(display, window.desktop, ev->event, ev->root_x,
ev->root_y, &target_x, &target_y, &child);
}
}
auto events = ev->generate_events(target, child, target_x, target_y);
XUngrabPointer(display, CurrentTime);
for (auto it : events) {
auto ev = std::get<1>(it);
XSendEvent(display, target, True, std::get<0>(it), ev);
free(ev);
}
XFlush(display);
}
#endif
void propagate_x11_event(XEvent &ev, const void *cookie) {
bool focus = ev.type == ButtonPress;
// cookie must be allocated before propagation, and freed after
#ifdef BUILD_XINPUT
if (ev.type == GenericEvent && ev.xgeneric.extension == window.xi_opcode) {
if (cookie == nullptr) { return; }
return propagate_xinput_event(
reinterpret_cast<const conky::xi_event_data *>(cookie));
}
#endif
if (!(ev.type == KeyPress || ev.type == KeyRelease ||
ev.type == ButtonPress || ev.type == ButtonRelease ||
ev.type == MotionNotify || ev.type == EnterNotify ||
ev.type == LeaveNotify)) {
// Not a known input event; blindly propagating them causes loops and all
// sorts of other evil.
return;
}
// Note that using ev.xbutton is the same as using any of the above events.
// It's only important we don't access fields that are not common to all of
// them.
ev.xbutton.window = window.desktop;
ev.xbutton.x = ev.xbutton.x_root;
ev.xbutton.y = ev.xbutton.y_root;
ev.xbutton.time = CurrentTime;
/* forward the event to the window below conky (e.g. caja) or desktop */
{
std::vector<Window> below = query_x11_windows_at_pos(
display, ev.xbutton.x_root, ev.xbutton.y_root,
[](XWindowAttributes &a) { return a.map_state == IsViewable; });
auto it = std::remove_if(below.begin(), below.end(),
[](Window w) { return w == window.window; });
below.erase(it, below.end());
if (!below.empty()) {
ev.xbutton.window = below.back();
Window _ignore; Window _ignore;
// Update event x and y coordinates to be target window relative // Update event x and y coordinates to be target window relative
XTranslateCoordinates(display, window.root, i_ev->common.window, XTranslateCoordinates(display, window.root, ev.xbutton.window,
i_ev->common.x_root, i_ev->common.y_root, ev.xbutton.x_root, ev.xbutton.y_root, &ev.xbutton.x,
&i_ev->common.x, &i_ev->common.y, &_ignore); &ev.xbutton.y, &_ignore);
} }
// drop below vector // drop below vector
} }
int mask =
ev_to_mask(ev.type, ev.type == ButtonRelease ? ev.xbutton.button : 0);
XUngrabPointer(display, CurrentTime); XUngrabPointer(display, CurrentTime);
XSendEvent(display, i_ev->common.window, True, ev_to_mask(i_ev->type), &ev); XSendEvent(display, ev.xbutton.window, True, mask, &ev);
if (focus) { if (focus) {
XSetInputFocus(display, i_ev->common.window, RevertToParent, CurrentTime); XSetInputFocus(display, ev.xbutton.window, RevertToParent, CurrentTime);
} }
} }
/// @brief This function returns the last descendant of a window (leaf) on the Window query_x11_top_parent(Display *display, Window child) {
/// graph. Window root = DefaultVRootWindow(display);
///
/// This function assumes the window stack below `parent` is linear. If it
/// isn't, it's only guaranteed that _some_ descendant of `parent` will be
/// returned. If provided `parent` has no descendants, the `parent` is returned.
Window query_x11_last_descendant(Display *display, Window parent) {
Window _ignored, *children;
std::uint32_t count;
Window current = parent; if (child == None || child == root) return child;
while (XQueryTree(display, current, &_ignored, &_ignored, &children, Window ret_root, parent, *children;
&count) == Success && std::uint32_t child_count;
count != 0) {
current = children[count - 1]; Window current = child;
XFree(children); int i;
do {
if (XQueryTree(display, current, &ret_root, &parent, &children,
&child_count) == 0) {
break;
} }
if (child_count != 0) XFree(children);
if (parent == root) break;
current = parent;
} while (true);
return current; return current;
} }
std::vector<Window> query_x11_windows(Display *display) { std::vector<Window> x11_atom_window_list(Display *display, Window window,
// _NET_CLIENT_LIST_STACKING Atom atom) {
Window root = DefaultRootWindow(display);
Atom clients_atom = XInternAtom(display, "_NET_CLIENT_LIST_STACKING", 0);
Atom actual_type; Atom actual_type;
int actual_format; int actual_format;
unsigned long nitems; unsigned long nitems;
unsigned long bytes_after; unsigned long bytes_after;
unsigned char *data = nullptr; unsigned char *data = nullptr;
// try retrieving ordered windows first: if (XGetWindowProperty(display, window, atom, 0, (~0L), False, XA_WINDOW,
if (XGetWindowProperty(display, root, clients_atom, 0, 0, False, XA_WINDOW,
&actual_type, &actual_format, &nitems, &bytes_after, &actual_type, &actual_format, &nitems, &bytes_after,
&data) == Success) { &data) == Success) {
free(data); if (actual_format == XA_WINDOW && nitems > 0) {
size_t count = bytes_after / 4;
if (XGetWindowProperty(display, root, clients_atom, 0, bytes_after / 4,
False, XA_WINDOW, &actual_type, &actual_format,
&nitems, &bytes_after, &data) == Success) {
Window *wdata = reinterpret_cast<Window *>(data); Window *wdata = reinterpret_cast<Window *>(data);
std::vector<Window> result(wdata, wdata + nitems); std::vector<Window> result(wdata, wdata + nitems);
free(data); XFree(data);
return result; return result;
} }
} }
clients_atom = XInternAtom(display, "_NET_CLIENT_LIST", 0); return std::vector<Window>{};
if (XGetWindowProperty(display, root, clients_atom, 0, 0, False, XA_WINDOW, }
&actual_type, &actual_format, &nitems, &bytes_after,
&data) == Success) {
free(data);
size_t count = bytes_after / 4;
if (XGetWindowProperty(display, root, clients_atom, 0, count, False, std::vector<Window> query_x11_windows(Display *display) {
XA_WINDOW, &actual_type, &actual_format, &nitems, Window root = DefaultRootWindow(display);
&bytes_after, &data) == Success) {
Window *wdata = reinterpret_cast<Window *>(data);
std::vector<Window> result(wdata, wdata + nitems);
free(data);
return result;
}
}
// slowest method that also returns inaccurate results: Atom clients_atom = ATOM(_NET_CLIENT_LIST_STACKING);
std::vector<Window> result =
x11_atom_window_list(display, root, clients_atom);
if (result.empty()) { return result; }
// TODO: How do we remove window decorations and other unwanted WM/DE junk clients_atom = ATOM(_NET_CLIENT_LIST);
// from this? result = x11_atom_window_list(display, root, clients_atom);
if (result.empty()) { return result; }
std::vector<Window> result; // slowest method
std::vector<Window> queue = {root};
std::vector<Window> queue = {DefaultVRootWindow(display)};
Window _ignored, *children; Window _ignored, *children;
std::uint32_t count; std::uint32_t count;
const auto has_wm_hints = [&](Window window) {
auto hints = XGetWMHints(display, window);
bool result = hints != NULL;
if (result) XFree(hints);
return result;
};
while (!queue.empty()) { while (!queue.empty()) {
Window current = queue.back(); Window current = queue.back();
queue.pop_back(); queue.pop_back();
if (XQueryTree(display, current, &_ignored, &_ignored, &children, &count) == if (XQueryTree(display, current, &_ignored, &_ignored, &children, &count)) {
Success && for (size_t i = 0; i < count; i++) queue.push_back(children[i]);
count != 0) { if (has_wm_hints(current)) result.push_back(current);
for (size_t i = 0; i < count; i++) { if (count > 0) XFree(children);
queue.push_back(children[i]);
result.push_back(current);
}
XFree(children);
} }
} }
@ -1515,7 +1583,7 @@ std::vector<Window> query_x11_windows(Display *display) {
} }
Window query_x11_window_at_pos(Display *display, int x, int y) { Window query_x11_window_at_pos(Display *display, int x, int y) {
Window root = DefaultRootWindow(display); Window root = DefaultVRootWindow(display);
// these values are ignored but NULL can't be passed to XQueryPointer. // these values are ignored but NULL can't be passed to XQueryPointer.
Window root_return; Window root_return;
@ -1535,7 +1603,7 @@ std::vector<Window> query_x11_windows_at_pos(
std::function<bool(XWindowAttributes &)> predicate) { std::function<bool(XWindowAttributes &)> predicate) {
std::vector<Window> result; std::vector<Window> result;
Window root = DefaultRootWindow(display); Window root = DefaultVRootWindow(display);
XWindowAttributes attr; XWindowAttributes attr;
for (Window current : query_x11_windows(display)) { for (Window current : query_x11_windows(display)) {

View File

@ -24,6 +24,7 @@
#pragma once #pragma once
#include "config.h"
#include "setting.hh" #include "setting.hh"
#include <X11/Xatom.h> #include <X11/Xatom.h>
@ -122,42 +123,22 @@ void set_struts(int);
void x11_init_window(lua::state &l, bool own); void x11_init_window(lua::state &l, bool own);
void deinit_x11(); void deinit_x11();
// Fields common to all X11 input events /// @brief Forwards argument event to the top-most window at event positon that
struct InputEventCommon { /// isn't conky.
int type; /* event type */ ///
uint64_t serial; /* # of last request processed by server */ /// Calling this function is time sensitive as it will query window at event
Bool send_event; /* true if this came from a SendEvent request */ /// position **at invocation time**.
Display *display; /* Display the event was read from */ /// @param event event to forward
Window window; /* "event" window reported relative to */ /// @param cookie optional cookie data
Window root; /* root window that the event occurred on */ void propagate_x11_event(XEvent &event, const void *cookie = nullptr);
Window subwindow; /* child window */
Time time; /* milliseconds */
int32_t x, y; /* pointer x, y coordinates in event window */
int32_t x_root, y_root; /* coordinates relative to root */
uint32_t state; /* key or button mask */
};
union InputEvent { /// @brief Returns a list of window values for the given atom.
int type; // event type /// @param display display with which the atom is associated
/// @param window window to query for the atom value
InputEventCommon common; /// @param atom atom to query for
/// @return a list of window values for the given atom
// Discrete interfaces std::vector<Window> x11_atom_window_list(Display *display, Window window,
XAnyEvent xany; // common event interface Atom atom);
XKeyEvent xkey; // KeyPress & KeyRelease events
XButtonEvent xbutton; // ButtonPress & ButtonRelease events
XMotionEvent xmotion; // MotionNotify event
XCrossingEvent xcrossing; // EnterNotify & LeaveNotify events
// Ensures InputEvent matches memory layout of XEvent.
// Accessing base variant is as code smell.
XEvent base;
};
// Returns InputEvent pointer to provided XEvent is an input event; nullptr
// otherwise.
InputEvent *xev_as_input_event(XEvent &ev);
void propagate_x11_event(XEvent &ev, const void *cookie);
/// @brief Tries getting a list of windows ordered from bottom to top. /// @brief Tries getting a list of windows ordered from bottom to top.
/// ///
@ -174,15 +155,15 @@ void propagate_x11_event(XEvent &ev, const void *cookie);
/// list of windows /// list of windows
std::vector<Window> query_x11_windows(Display *display); std::vector<Window> query_x11_windows(Display *display);
/// @brief Finds the last descendant of a window (leaf) on the graph. /// @brief Finds the last ascendant of a window (trunk) before root.
/// ///
/// This function assumes the window stack below `parent` is linear. If it /// If provided `child` is root or has no windows between root and itself, the
/// isn't, it's only guaranteed that _some_ descendant of `parent` will be /// `child` is returned.
/// returned. If provided `parent` has no descendants, the `parent` is returned.
/// ///
/// @param display display of parent /// @param display display of parent
/// @return a descendant window /// @param child window whose parents to query
Window query_x11_last_descendant(Display *display, Window parent); /// @return the top level ascendant window
Window query_x11_top_parent(Display *display, Window child);
/// @brief Returns the top-most window overlapping provided screen coordinates. /// @brief Returns the top-most window overlapping provided screen coordinates.
/// ///