Skip to content

Commit

Permalink
Add most missing XInput event code
Browse files Browse the repository at this point in the history
`BUILD_XINPUT` feature should use xinput instead of old X11 input events
whenever possible. It's simpler to write now that it no longer makes
main_loop_wait 100LoC larger.

Also added proper valuator resolution by name, these values are mostly
the same, but some devices might have unexpected indices. Ideally, names
should also be configurable at runtime.

Signed-off-by: Tin Švagelj <tin.svagelj@live.com>
  • Loading branch information
Caellian committed Apr 9, 2024
1 parent 67f3bb2 commit 1a6a8e0
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 56 deletions.
158 changes: 130 additions & 28 deletions src/display-x11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

#include <cstdint>
#include <iostream>
#include <map>
#include <sstream>
#include <unordered_map>
#include <vector>
Expand Down Expand Up @@ -428,7 +429,53 @@ bool display_output_x11::main_loop_wait(double t) {
#ifdef OWN_WINDOW
#ifdef BUILD_MOUSE_EVENTS
#ifdef BUILD_XINPUT
typedef std::map<int, XIDeviceInfo *> XIDeviceInfoMap;
static XIDeviceInfoMap xi_device_info_cache{};
XIDeviceInfo *xi_device_info(int device_id) {
if (xi_device_info_cache.count(device_id)) {
return xi_device_info_cache[device_id];
}

int num_devices;
XIDeviceInfo *info = XIQueryDevice(display, device_id, &num_devices);
if (num_devices == 0) { return nullptr; }

xi_device_info_cache[device_id] = info;
return xi_device_info_cache[device_id];
}

int valuator_index(int device_id, char *name) {
char *name = name;
if (name == nullptr || strlen(name) == 0) { name = "None"; }

XIDeviceInfo *device = xi_device_info(device_id);

for (int i = 0; i < device->num_classes; i++) {
if (device->classes[i]->type != XIValuatorClass) continue;

XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)device->classes[i];

auto label = x11_atom_string(display, window.root, class_info->label);
if (strcmp(label.c_str(), name)) return i;
}

return -1;
}

EV_HANDLER(mouse_input) {
if (ev.type == ButtonPress || ev.type == ButtonRelease ||
ev.type == MotionNotify) {
// We'll pass basic X11 events through, and instead deal with XInput events.

// FIXME: This is wrong. lua hooks dictate whether events should be
// consumed. So each XInput event has a corresponding X11 event we need to
// also block based on the return value.
//*consumed = false;
// We could also fabricate a basic event from XInput one as XInput contains
// all the needed data...
return true;
}

if (ev.type != GenericEvent || ev.xcookie.extension != window.xi_opcode)
return false;

Expand All @@ -439,52 +486,107 @@ EV_HANDLER(mouse_input) {

auto *data = reinterpret_cast<XIDeviceEvent *>(ev.xcookie.data);

if (data->evtype == XI_DeviceChanged) {
int device_id = data->sourceid;

// update cached device info
if (xi_device_info_cache.count(device_id)) {
XIFreeDeviceInfo(xi_device_info_cache[device_id]);
int num_devices;
xi_device_info_cache[device_id] =
XIQueryDevice(display, device_id, &num_devices);
if (num_devices == 0) { xi_device_info_cache.erase(device_id); }
}
return true;
}

// TODO: Make valuator_index names configurable?
int hor_move = valuator_index(data->deviceid, "Rel X"); // Almost always 0
int vert_move = valuator_index(data->deviceid, "Rel Y"); // Almost always 1

// the only way to differentiate between a scroll and move event is
// though valuators - move has first 2 set, other axis movements have
// other.
// FIXME: Use dynamic valuator indices
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++) {
for (std::size_t i = 0; 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);
Window event_window;
modifier_state_t mods;
if (data->evtype == XI_Motion || data->evtype == XI_ButtonPress ||
data->evtype == XI_ButtonRelease) {
event_window = 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) {
event_window = query_x11_last_descendant(display, event_window);
mods = x11_modifier_state(data->mods.effective);
}

bool cursor_over_conky =
event_window == window.window &&
(window.window != NULL ||
(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 (data->evtype == XI_Motion) {
if (is_cursor_move) {
static bool cursor_inside = false;

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_ENTER, 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));
cursor_inside = false;
}
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;
} else {
// up neg 2
// down pos 2
// left neg 3
// right pos 3
scroll_direction_t direction =
scroll_direction_t::SCROLL_DOWN; // FIXME: Detect from valuators
*consumed = llua_mouse_hook(
mouse_scroll_event(data->event_x, data->event_y, data->root_x,
data->root_y, direction, mods));
}
} else if (cursor_over_conky && (data->evtype == XI_ButtonPress ||
data->evtype == XI_ButtonRelease)) {
if (data->detail >= 4 && data->detail <= 7) {
if (data->evtype == XI_ButtonRelease) return true;

scroll_direction_t direction = x11_scroll_direction(data->detail);
*consumed = llua_mouse_hook(
mouse_scroll_event(data->event_x, data->event_y, data->root_x,
data->root_y, direction, mods));
}

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));

if (data->evtype == XI_ButtonPress) { *focus = *consumed; }
}
// TODO: Handle other events

XFreeEventData(display, &ev.xcookie);
XFreeEventData(display, &ev.xcookie); // TODO:
return true;
}
#else /* BUILD_XINPUT */
Expand Down Expand Up @@ -733,7 +835,7 @@ void process_surface_events(conky::display_output_x11 *surface,
*/
bool consumed = true;
bool focus = false;
process_event(surface, display, ev, &consumed, &focus);
bool _handled = process_event(surface, display, ev, &consumed, &focus);

if (!consumed) {
propagate_x11_event(ev);
Expand Down
63 changes: 35 additions & 28 deletions src/x11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1413,12 +1413,6 @@ void propagate_x11_event(XEvent &ev) {
XSendEvent(display, i_ev->common.window, False, ev_to_mask(i_ev->type), &ev);
}

/// @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;
Expand All @@ -1435,52 +1429,65 @@ Window query_x11_last_descendant(Display *display, Window parent) {
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::string x11_atom_string(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, 1024, False, AnyPropertyType,
&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) {
Window *wdata = reinterpret_cast<Window *>(data);
std::vector<Window> result(wdata, wdata + nitems);
free(data);
auto result = std::string(reinterpret_cast<char *>(data));
XFree(data);
if (actual_type == XA_STRING && actual_format == 8) {
return result;
} else {
return std::string();
}
}
}

clients_atom = XInternAtom(display, "_NET_CLIENT_LIST", 0);
if (XGetWindowProperty(display, root, clients_atom, 0, 0, False, XA_WINDOW,
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;

if (XGetWindowProperty(display, window, atom, 0, 0, False, XA_WINDOW,
&actual_type, &actual_format, &nitems, &bytes_after,
&data) == Success) {
free(data);
XFree(data);
size_t count = bytes_after / 4;

if (XGetWindowProperty(display, root, clients_atom, 0, count, False,
if (XGetWindowProperty(display, window, atom, 0, bytes_after / 4, 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);
XFree(data);
return result;
}
}

return std::vector<Window>{};
}

std::vector<Window> query_x11_windows(Display *display) {
Window root = DefaultRootWindow(display);

Atom clients_atom = XInternAtom(display, "_NET_CLIENT_LIST_STACKING", 0);
std::vector<Window> result =
x11_atom_window_list(display, root, clients_atom);
if (result.empty()) { return result; }

clients_atom = XInternAtom(display, "_NET_CLIENT_LIST", 0);
result = x11_atom_window_list(display, root, clients_atom);
if (result.empty()) { return result; }

// slowest method that also returns inaccurate results:

// TODO: How do we remove window decorations and other unwanted WM/DE junk
Expand Down
4 changes: 4 additions & 0 deletions src/x11.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ union InputEvent {
InputEvent *xev_as_input_event(XEvent &ev);
void propagate_x11_event(XEvent &ev);

std::string x11_atom_string(Display *display, Window window, Atom 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.
///
/// Whether the list is correctly ordered depends on WM/DE providing the
Expand Down

0 comments on commit 1a6a8e0

Please sign in to comment.