mirror of
https://github.com/Llewellynvdm/conky.git
synced 2025-01-16 04:02:15 +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:
parent
d56c0372f5
commit
f4b3229fc3
@ -53,7 +53,9 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
@ -442,72 +444,156 @@ bool handle_event(conky::display_output_x11 *surface, Display *display,
|
||||
}
|
||||
|
||||
#ifdef OWN_WINDOW
|
||||
#ifdef BUILD_XINPUT
|
||||
template <>
|
||||
bool handle_event<x_event_handler::XINPUT_MOTION>(
|
||||
conky::display_output_x11 *surface, Display *display, XEvent &ev,
|
||||
bool *consumed, void **cookie) {
|
||||
if (ev.type != GenericEvent || ev.xcookie.extension != window.xi_opcode)
|
||||
return false;
|
||||
|
||||
if (!XGetEventData(display, &ev.xcookie)) {
|
||||
NORM_ERR("unable to get XInput event data");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *data = reinterpret_cast<XIDeviceEvent *>(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) {
|
||||
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;
|
||||
}
|
||||
#endif /* BUILD_XINPUT */
|
||||
template <>
|
||||
bool handle_event<x_event_handler::MOUSE_INPUT>(
|
||||
conky::display_output_x11 *surface, Display *display, XEvent &ev,
|
||||
bool *consumed, void **cookie) {
|
||||
#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;
|
||||
|
||||
if (!XGetEventData(display, &ev.xcookie)) {
|
||||
// already consumed
|
||||
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 = xi_event_data::read_cookie(display, ev.xcookie.data);
|
||||
XFreeEventData(display, &ev.xcookie);
|
||||
if (data == nullptr) {
|
||||
// we ate the cookie, Xi event not handled
|
||||
return true;
|
||||
}
|
||||
*cookie = data;
|
||||
|
||||
Window event_window =
|
||||
query_x11_window_at_pos(display, data->root_x, data->root_y);
|
||||
// query_result is not window.window in some cases.
|
||||
modifier_state_t mods = x11_modifier_state(data->mods.effective);
|
||||
|
||||
bool same_window = query_x11_top_parent(display, event_window) ==
|
||||
query_x11_top_parent(display, window.window);
|
||||
bool cursor_over_conky = same_window && data->root_x >= window.x &&
|
||||
data->root_x < (window.x + window.width) &&
|
||||
data->root_y >= window.y &&
|
||||
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_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;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
mouse_event_t type = mouse_event_t::MOUSE_PRESS;
|
||||
if (data->evtype == XI_ButtonRelease) {
|
||||
type = mouse_event_t::MOUSE_RELEASE;
|
||||
}
|
||||
|
||||
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 &&
|
||||
ev.type != MotionNotify)
|
||||
return false;
|
||||
@ -551,7 +637,7 @@ bool handle_event<x_event_handler::MOUSE_INPUT>(
|
||||
// always propagate mouse input if not handling mouse events
|
||||
*consumed = false;
|
||||
#endif /* BUILD_MOUSE_EVENTS */
|
||||
|
||||
#endif /* BUILD_XINPUT */
|
||||
if (!own_window.get(*state)) return true;
|
||||
switch (own_window_type.get(*state)) {
|
||||
case window_type::TYPE_NORMAL:
|
||||
@ -751,9 +837,9 @@ void process_surface_events(conky::display_output_x11 *surface,
|
||||
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.
|
||||
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;
|
||||
void *cookie = nullptr;
|
||||
|
@ -27,8 +27,20 @@
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
#ifdef BUILD_XINPUT
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#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 {
|
||||
@ -206,4 +218,416 @@ void mouse_button_event::push_lua_data(lua_State *L) const {
|
||||
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
|
@ -28,9 +28,25 @@
|
||||
#include "config.h"
|
||||
#include "logging.h"
|
||||
|
||||
#ifdef BUILD_XINPUT
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#endif /* BUILD_XINPUT */
|
||||
|
||||
extern "C" {
|
||||
#ifdef BUILD_X11
|
||||
#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 */
|
||||
|
||||
#include <lua.h>
|
||||
@ -41,7 +57,7 @@ extern "C" {
|
||||
#include <dev/evdev/input-event-codes.h>
|
||||
#elif __DragonFly__
|
||||
#include <dev/misc/evdev/input-event-codes.h>
|
||||
#else
|
||||
#else /* platform */
|
||||
// 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.
|
||||
|
||||
@ -56,7 +72,7 @@ extern "C" {
|
||||
#define BTN_BACK 0x116
|
||||
// Forward mouse button event code
|
||||
#define BTN_FORWARD 0x115
|
||||
#endif
|
||||
#endif /* platform */
|
||||
}
|
||||
|
||||
namespace conky {
|
||||
@ -227,6 +243,84 @@ struct mouse_crossing_event : public mouse_positioned_event {
|
||||
: 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
|
||||
|
||||
#endif /* MOUSE_EVENTS_H */
|
||||
|
434
src/x11.cc
434
src/x11.cc
@ -37,6 +37,12 @@
|
||||
#include "gui.h"
|
||||
#include "logging.h"
|
||||
|
||||
#ifdef BUILD_XINPUT
|
||||
#include "mouse-events.h"
|
||||
|
||||
#include <vector>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
@ -44,6 +50,7 @@
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wvariadic-macros"
|
||||
#pragma GCC diagnostic ignored "-Wregister"
|
||||
@ -73,13 +80,14 @@
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#endif /* BUILD_XFIXES */
|
||||
#ifdef BUILD_XINPUT
|
||||
#include <X11/extensions/XInput.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
#include <vector>
|
||||
#endif /* BUILD_XINPUT */
|
||||
#ifdef HAVE_XCB_ERRORS
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_errors.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
/* some basic X11 stuff */
|
||||
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));
|
||||
}
|
||||
|
||||
/// @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 */
|
||||
static void init_x11() {
|
||||
DBGP("enter init_x11()");
|
||||
@ -416,75 +470,21 @@ static void update_workarea() {
|
||||
* Return desktop window on success,
|
||||
* and set root and desktop byref return values.
|
||||
* Return 0 on failure. */
|
||||
static Window find_desktop_window(Window *p_root, Window *p_desktop) {
|
||||
Atom type;
|
||||
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);
|
||||
static Window find_desktop_window(Window root) {
|
||||
Window desktop = root;
|
||||
|
||||
/* get subwindows from root */
|
||||
win = find_subwindow(root, -1, -1);
|
||||
|
||||
desktop = find_subwindow(root, -1, -1);
|
||||
update_workarea();
|
||||
desktop = find_subwindow(desktop, workarea[2], workarea[3]);
|
||||
|
||||
win = find_subwindow(win, workarea[2], workarea[3]);
|
||||
|
||||
if (buf != nullptr) {
|
||||
XFree(buf);
|
||||
buf = nullptr;
|
||||
}
|
||||
|
||||
if (win != root) {
|
||||
fprintf(stderr,
|
||||
PACKAGE_NAME
|
||||
": desktop window (%lx) is subwindow of root window (%lx)\n",
|
||||
win, root);
|
||||
if (desktop != root) {
|
||||
DBGP2("desktop window (0x%lx) is subwindow of root window (0x%lx)", desktop,
|
||||
root);
|
||||
} else {
|
||||
fprintf(stderr, PACKAGE_NAME ": desktop window (%lx) is root window\n",
|
||||
win);
|
||||
DBGP2("desktop window (0x%lx) is root window", desktop);
|
||||
}
|
||||
|
||||
fflush(stderr);
|
||||
|
||||
*p_root = root;
|
||||
*p_desktop = win;
|
||||
|
||||
return win;
|
||||
return desktop;
|
||||
}
|
||||
|
||||
#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
|
||||
(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
|
||||
if (own) {
|
||||
int depth = 0, flags = CWOverrideRedirect | CWBackingStore;
|
||||
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;
|
||||
visual = CopyFromParent;
|
||||
#ifdef BUILD_ARGB
|
||||
@ -909,16 +912,7 @@ void x11_init_window(lua::state &l, bool own) {
|
||||
{
|
||||
XWindowAttributes attrs;
|
||||
|
||||
if (window.window == 0u) {
|
||||
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 (window.window == None) { window.window = window.desktop; }
|
||||
|
||||
if (XGetWindowAttributes(display, window.window, &attrs) != 0) {
|
||||
window.width = attrs.width;
|
||||
@ -939,14 +933,10 @@ void x11_init_window(lua::state &l, bool own) {
|
||||
input_mask |= StructureNotifyMask | ButtonPressMask | ButtonReleaseMask;
|
||||
}
|
||||
#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;
|
||||
#ifdef BUILD_XINPUT
|
||||
do { // not loop
|
||||
// not a loop; substitutes goto with break - if checks fail
|
||||
do {
|
||||
int _ignored; // segfault if NULL
|
||||
if (!XQueryExtension(display, "XInputExtension", &window.xi_opcode,
|
||||
&_ignored, &_ignored)) {
|
||||
@ -955,8 +945,8 @@ void x11_init_window(lua::state &l, bool own) {
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t major = 2, minor = 0;
|
||||
uint32_t retval = XIQueryVersion(display, &major, &minor);
|
||||
int major = 2, minor = 0;
|
||||
int retval = XIQueryVersion(display, &major, &minor);
|
||||
if (retval != Success) {
|
||||
NORM_ERR("Error: XInput 2.0 is not supported!");
|
||||
break;
|
||||
@ -964,18 +954,49 @@ void x11_init_window(lua::state &l, bool own) {
|
||||
|
||||
const std::size_t mask_size = (XI_LASTEVENT + 7) / 8;
|
||||
unsigned char mask_bytes[mask_size] = {0}; /* must be zeroed! */
|
||||
XISetMask(mask_bytes, XI_HierarchyChanged);
|
||||
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];
|
||||
ev_masks[0].deviceid = XIAllDevices;
|
||||
ev_masks[0].mask_len = sizeof(mask_bytes);
|
||||
ev_masks[0].mask = mask_bytes;
|
||||
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;
|
||||
} while (false);
|
||||
#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) {
|
||||
input_mask |= EnterWindowMask | LeaveWindowMask;
|
||||
input_mask |= PointerMotionMask | EnterWindowMask | LeaveWindowMask;
|
||||
}
|
||||
#endif /* BUILD_MOUSE_EVENTS */
|
||||
#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));
|
||||
}
|
||||
|
||||
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
|
||||
/// @param event_type Xlib event type
|
||||
/// @return Xlib event mask
|
||||
int ev_to_mask(int event_type) {
|
||||
int ev_to_mask(int event_type, int button) {
|
||||
switch (event_type) {
|
||||
case KeyPress:
|
||||
return KeyPressMask;
|
||||
@ -1364,7 +1375,20 @@ int ev_to_mask(int event_type) {
|
||||
case ButtonPress:
|
||||
return ButtonPressMask;
|
||||
case ButtonRelease:
|
||||
return ButtonReleaseMask;
|
||||
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;
|
||||
}
|
||||
case EnterNotify:
|
||||
return EnterWindowMask;
|
||||
case LeaveNotify:
|
||||
@ -1376,138 +1400,182 @@ int ev_to_mask(int event_type) {
|
||||
}
|
||||
}
|
||||
|
||||
void propagate_x11_event(XEvent &ev, const void *cookie) {
|
||||
bool focus = ev.type == ButtonPress;
|
||||
|
||||
InputEvent *i_ev = xev_as_input_event(ev);
|
||||
if (i_ev == nullptr) {
|
||||
// Not a known input event; blindly propagating them causes loops and all
|
||||
// sorts of other evil.
|
||||
#ifdef BUILD_XINPUT
|
||||
void propagate_xinput_event(const conky::xi_event_data *ev) {
|
||||
if (ev->evtype != XI_Motion && ev->evtype != XI_ButtonPress &&
|
||||
ev->evtype != XI_ButtonRelease) {
|
||||
return;
|
||||
}
|
||||
|
||||
i_ev->common.window = window.desktop;
|
||||
i_ev->common.x = i_ev->common.x_root;
|
||||
i_ev->common.y = i_ev->common.y_root;
|
||||
i_ev->common.time = CurrentTime;
|
||||
|
||||
/* forward the event to the window below conky (e.g. caja) or desktop */
|
||||
Window target = window.root;
|
||||
Window child = None;
|
||||
int target_x = ev->event_x;
|
||||
int target_y = ev->event_y;
|
||||
{
|
||||
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; });
|
||||
auto it = std::remove_if(below.begin(), below.end(),
|
||||
[](Window w) { return w == window.window; });
|
||||
below.erase(it, below.end());
|
||||
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;
|
||||
// Update event x and y coordinates to be target window relative
|
||||
XTranslateCoordinates(display, window.root, i_ev->common.window,
|
||||
i_ev->common.x_root, i_ev->common.y_root,
|
||||
&i_ev->common.x, &i_ev->common.y, &_ignore);
|
||||
XTranslateCoordinates(display, window.root, ev.xbutton.window,
|
||||
ev.xbutton.x_root, ev.xbutton.y_root, &ev.xbutton.x,
|
||||
&ev.xbutton.y, &_ignore);
|
||||
}
|
||||
// drop below vector
|
||||
}
|
||||
|
||||
int mask =
|
||||
ev_to_mask(ev.type, ev.type == ButtonRelease ? ev.xbutton.button : 0);
|
||||
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) {
|
||||
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
|
||||
/// graph.
|
||||
///
|
||||
/// 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 query_x11_top_parent(Display *display, Window child) {
|
||||
Window root = DefaultVRootWindow(display);
|
||||
|
||||
Window current = parent;
|
||||
if (child == None || child == root) return child;
|
||||
|
||||
while (XQueryTree(display, current, &_ignored, &_ignored, &children,
|
||||
&count) == Success &&
|
||||
count != 0) {
|
||||
current = children[count - 1];
|
||||
XFree(children);
|
||||
}
|
||||
Window ret_root, parent, *children;
|
||||
std::uint32_t child_count;
|
||||
|
||||
Window current = child;
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<Window> query_x11_windows(Display *display) {
|
||||
// _NET_CLIENT_LIST_STACKING
|
||||
Window root = DefaultRootWindow(display);
|
||||
|
||||
Atom clients_atom = XInternAtom(display, "_NET_CLIENT_LIST_STACKING", 0);
|
||||
|
||||
std::vector<Window> x11_atom_window_list(Display *display, Window window,
|
||||
Atom atom) {
|
||||
Atom actual_type;
|
||||
int actual_format;
|
||||
unsigned long nitems;
|
||||
unsigned long bytes_after;
|
||||
unsigned char *data = nullptr;
|
||||
|
||||
// try retrieving ordered windows first:
|
||||
if (XGetWindowProperty(display, root, clients_atom, 0, 0, False, XA_WINDOW,
|
||||
if (XGetWindowProperty(display, window, atom, 0, (~0L), 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, bytes_after / 4,
|
||||
False, XA_WINDOW, &actual_type, &actual_format,
|
||||
&nitems, &bytes_after, &data) == Success) {
|
||||
if (actual_format == XA_WINDOW && nitems > 0) {
|
||||
Window *wdata = reinterpret_cast<Window *>(data);
|
||||
std::vector<Window> result(wdata, wdata + nitems);
|
||||
free(data);
|
||||
XFree(data);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
clients_atom = XInternAtom(display, "_NET_CLIENT_LIST", 0);
|
||||
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;
|
||||
return std::vector<Window>{};
|
||||
}
|
||||
|
||||
if (XGetWindowProperty(display, root, clients_atom, 0, count, False,
|
||||
XA_WINDOW, &actual_type, &actual_format, &nitems,
|
||||
&bytes_after, &data) == Success) {
|
||||
Window *wdata = reinterpret_cast<Window *>(data);
|
||||
std::vector<Window> result(wdata, wdata + nitems);
|
||||
free(data);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
std::vector<Window> query_x11_windows(Display *display) {
|
||||
Window root = DefaultRootWindow(display);
|
||||
|
||||
// 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
|
||||
// from this?
|
||||
clients_atom = ATOM(_NET_CLIENT_LIST);
|
||||
result = x11_atom_window_list(display, root, clients_atom);
|
||||
if (result.empty()) { return result; }
|
||||
|
||||
std::vector<Window> result;
|
||||
std::vector<Window> queue = {root};
|
||||
// slowest method
|
||||
|
||||
std::vector<Window> queue = {DefaultVRootWindow(display)};
|
||||
|
||||
Window _ignored, *children;
|
||||
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()) {
|
||||
Window current = queue.back();
|
||||
queue.pop_back();
|
||||
if (XQueryTree(display, current, &_ignored, &_ignored, &children, &count) ==
|
||||
Success &&
|
||||
count != 0) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
queue.push_back(children[i]);
|
||||
result.push_back(current);
|
||||
}
|
||||
XFree(children);
|
||||
if (XQueryTree(display, current, &_ignored, &_ignored, &children, &count)) {
|
||||
for (size_t i = 0; i < count; i++) queue.push_back(children[i]);
|
||||
if (has_wm_hints(current)) result.push_back(current);
|
||||
if (count > 0) 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 root = DefaultRootWindow(display);
|
||||
Window root = DefaultVRootWindow(display);
|
||||
|
||||
// these values are ignored but NULL can't be passed to XQueryPointer.
|
||||
Window root_return;
|
||||
@ -1535,7 +1603,7 @@ std::vector<Window> query_x11_windows_at_pos(
|
||||
std::function<bool(XWindowAttributes &)> predicate) {
|
||||
std::vector<Window> result;
|
||||
|
||||
Window root = DefaultRootWindow(display);
|
||||
Window root = DefaultVRootWindow(display);
|
||||
XWindowAttributes attr;
|
||||
|
||||
for (Window current : query_x11_windows(display)) {
|
||||
|
63
src/x11.h
63
src/x11.h
@ -24,6 +24,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
#include "setting.hh"
|
||||
|
||||
#include <X11/Xatom.h>
|
||||
@ -122,42 +123,22 @@ void set_struts(int);
|
||||
void x11_init_window(lua::state &l, bool own);
|
||||
void deinit_x11();
|
||||
|
||||
// Fields common to all X11 input events
|
||||
struct InputEventCommon {
|
||||
int type; /* event type */
|
||||
uint64_t serial; /* # of last request processed by server */
|
||||
Bool send_event; /* true if this came from a SendEvent request */
|
||||
Display *display; /* Display the event was read from */
|
||||
Window window; /* "event" window reported relative to */
|
||||
Window root; /* root window that the event occurred on */
|
||||
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 */
|
||||
};
|
||||
/// @brief Forwards argument event to the top-most window at event positon that
|
||||
/// isn't conky.
|
||||
///
|
||||
/// Calling this function is time sensitive as it will query window at event
|
||||
/// position **at invocation time**.
|
||||
/// @param event event to forward
|
||||
/// @param cookie optional cookie data
|
||||
void propagate_x11_event(XEvent &event, const void *cookie = nullptr);
|
||||
|
||||
union InputEvent {
|
||||
int type; // event type
|
||||
|
||||
InputEventCommon common;
|
||||
|
||||
// Discrete interfaces
|
||||
XAnyEvent xany; // common event interface
|
||||
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 Returns a list of window values for the given atom.
|
||||
/// @param display display with which the atom is associated
|
||||
/// @param window window to query for the atom value
|
||||
/// @param atom atom to query for
|
||||
/// @return a list of window values for the given atom
|
||||
std::vector<Window> x11_atom_window_list(Display *display, Window window,
|
||||
Atom atom);
|
||||
|
||||
/// @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
|
||||
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
|
||||
/// isn't, it's only guaranteed that _some_ descendant of `parent` will be
|
||||
/// returned. If provided `parent` has no descendants, the `parent` is returned.
|
||||
/// If provided `child` is root or has no windows between root and itself, the
|
||||
/// `child` is returned.
|
||||
///
|
||||
/// @param display display of parent
|
||||
/// @return a descendant window
|
||||
Window query_x11_last_descendant(Display *display, Window parent);
|
||||
/// @param child window whose parents to query
|
||||
/// @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.
|
||||
///
|
||||
|
Loading…
Reference in New Issue
Block a user