Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xinput support #115

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "qubes-common"]
path = qubes-common
url = [email protected]:locriacyber/qubes-gui-common.git
branch = xinput2
1 change: 1 addition & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Build-Depends:
libxrandr-dev,
libxcb1-dev,
libx11-xcb-dev,
libxi-dev,
libconfig-dev,
libpng-dev,
libnotify-dev,
Expand Down
4 changes: 4 additions & 0 deletions gui-daemon/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
qubes_guid
qubes-guid.1
/zig-cache/
/zig-out/
/include/
6 changes: 3 additions & 3 deletions gui-daemon/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
MAKEFLAGS := -rR
VCHAN_PKG = $(if $(BACKEND_VMM),vchan-$(BACKEND_VMM),vchan)
CC=gcc
pkgs := x11 xext x11-xcb xcb glib-2.0 $(VCHAN_PKG) libpng libnotify libconfig
objs := xside.o png.o trayicon.o ../gui-common/double-buffer.o ../gui-common/txrx-vchan.o \
pkgs := x11 xext x11-xcb xcb glib-2.0 xi $(VCHAN_PKG) libpng libnotify libconfig
objs := xside.o png.o trayicon.o xinput-plugin.o xutils.o ../gui-common/double-buffer.o ../gui-common/txrx-vchan.o \
../gui-common/error.o list.o
extra_cflags := -I../include/ -g -O2 -Wall -Wextra -Werror -pie -fPIC \
extra_cflags := -I../qubes-common/include -I../include/ -g -O2 -Wall -Wextra -Werror -pie -fPIC \
$(shell pkg-config --cflags $(pkgs)) \
-fvisibility=hidden \
-fno-strict-aliasing \
Expand Down
78 changes: 78 additions & 0 deletions gui-daemon/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const std = @import("std");

const csources = [_][]const u8{
"xside.c",
"xutils.c",
"png.c",
"trayicon.c",
"xinput-plugin.c",
"../gui-common/double-buffer.c",
"../gui-common/txrx-vchan.c",
"../gui-common/error.c",
"../common/list.c",
};

// TODO:
// VCHAN_PKG = $(if $(BACKEND_VMM),vchan-$(BACKEND_VMM),vchan)
const VCHAN_PKG = "vchan";
const packages = .{
"x11",
"xext",
"x11-xcb",
"xcb",
"xi",
VCHAN_PKG,
"libpng",
"libnotify", // TODO: replace with `notify-send`
"libconfig", // TODO: replace with simple toml parser
"libunwind",
};

fn setup_dependencies(exe: *std.build.LibExeObjStep) void {
exe.addIncludePath("../qubes-common/include");
exe.addIncludePath("../include");
// exe.addIncludePath("include");
// exe.addLibraryPath("/usr/lib");
// exe.addSystemIncludePath("/usr/include");
// exe.addIncludePath("/usr/include");
exe.linkLibC();
inline for (packages) |package| {
exe.linkSystemLibrary(package);
}
}

pub fn build(b: *std.build.Builder) void {
const target = b.standardTargetOptions(.{}); // -Dtarget
const mode = b.standardReleaseOptions(); // -Drelease-fast -Drelease-safe -Drelease-small

const exe = b.addExecutable("qubes-guid", "xside.c");
exe.setTarget(target);
exe.setBuildMode(mode);
setup_dependencies(exe);
exe.addCSourceFiles(csources[1..], &.{

});

// const obj_xinput_plug = b.addObject("qubes-daemon-xinput-plugin", "xinput-plugin.zig");
// setup_dependencies(obj_xinput_plug);
// obj_xinput_plug.addIncludePath(".");
// exe.addObject(obj_xinput_plug);

exe.install();

const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}

const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);

// const exe_tests = b.addTest("src/main.zig");
// exe_tests.setTarget(target);
// exe_tests.setBuildMode(mode);

// const test_step = b.step("test", "Run unit tests");
// test_step.dependOn(&exe_tests.step);
}
154 changes: 154 additions & 0 deletions gui-daemon/xinput-plugin.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include "xside.h"
#include "xutils.h"
#include "txrx.h"
#include <X11/extensions/XInput2.h>
#include "qubes-gui-protocol.h"


/* check and handle guid-special keys
* currently only for inter-vm clipboard copy
*/
static bool is_special_keypress_xinput(Ghandles * g, const XIDeviceEvent * ev, XID remote_winid)
{
// cast just enough fields to be accepted by `is_special_keypress
XKeyEvent xev;
xev.state = ev->mods.effective;
xev.keycode = ev->detail;
xev.type = ev->evtype;
xev.time = ev->time;
return is_special_keypress(g, &xev, remote_winid);
}

/* handle local XInput event: KeyPress, KeyRelease
* send it to relevant window in VM
*
* Note, no raw keys are press
*/
static void process_xinput_key(Ghandles * g, const XIDeviceEvent * ev)
{
CHECK_NONMANAGED_WINDOW(g, ev->event);
// yes, ev->event is the window number
update_wm_user_time(g, ev->event, ev->time);
if (ev->flags & XIKeyRepeat)
return; // don't send key repeat events
if (is_special_keypress_xinput(g, ev, vm_window->remote_winid))
return;

struct msg_hdr hdr;
hdr.type = MSG_XI_KEY;
hdr.window = vm_window->remote_winid;

struct msg_xi_key k;
k.evtype = ev->evtype;
k.device = ev->deviceid; // which device is this from? Not always a "keyboard"
k.detail = ev->detail; // key code
k.x = ev->event_x;
k.y = ev->event_y;
k.modifier_effective = ev->mods.effective;
k.flags = ev->flags;

write_message(g->vchan, hdr, k);
}

/* handle local XInput event: FocusIn, FocusOut
* send to relevant window in VM */
static void process_xinput_focus(Ghandles * g, const XILeaveEvent * ev)
{
CHECK_NONMANAGED_WINDOW(g, ev->event);
update_wm_user_time(g, ev->event, ev->time);

if (ev->type == XI_FocusIn) {
send_keymap_notify(g);
}

struct msg_hdr hdr;
hdr.type = MSG_XI_FOCUS;
hdr.window = vm_window->remote_winid;

struct msg_xi_focus k;
k.evtype = ev->evtype;
k.device = ev->deviceid; // which device is this from? Not always a "keyboard"
k.mode = ev->mode;
k.detail = ev->detail; // key code
k.x = ev->event_x;
k.y = ev->event_y;
k.modifier_effective = ev->mods.effective;
write_message(g->vchan, hdr, k);
}


static int xinput_plugin_enabled = false;

void qubes_daemon_xinput_plug__init(Ghandles * g) {
// qubes protocol version detection
uint32_t version_major, version_minor;
version_major = g->protocol_version >> 16;
version_minor = g->protocol_version & 0xffff;
if (version_major < 1 || version_minor < 5) {
fprintf(stderr, "X Input support disabled, client too old. Negotiated version: %d.%d.\n", version_major, version_minor);
return;
}
fprintf(stderr, "X Input support enabled.\n");


int ev_base, err_base; /* ignore */
if (!XQueryExtension(g->display, "XInputExtension", &g->xi_opcode, &ev_base, &err_base)) {
fprintf(stderr, "X Input extension not available. Key press events not available. Upgrade your X11 server now.\n");
exit(1);
}
xinput_plugin_enabled = true;
}

void qubes_daemon_xinput_plug__on_new_window(Ghandles * g, Window child_win) {
if (!xinput_plugin_enabled) return;

// select xinput events
XIEventMask xi_mask;
xi_mask.deviceid = XIAllMasterDevices; // https://stackoverflow.com/questions/44095001/getting-double-rawkeypress-events-using-xinput2
xi_mask.mask_len = XIMaskLen(XI_LASTEVENT);
if (!(xi_mask.mask = calloc(xi_mask.mask_len, sizeof(char)))) {
fputs("Out of memory!\n", stderr);
exit(1);
}
XISetMask(xi_mask.mask, XI_KeyPress);
XISetMask(xi_mask.mask, XI_KeyRelease);
XISetMask(xi_mask.mask, XI_FocusIn);
XISetMask(xi_mask.mask, XI_FocusOut);

int err = XISelectEvents(g->display, child_win, &xi_mask, 1);
if (err) {
fprintf(stderr, "Failed to subscribe to XI events. ErrCode: %d\n", err);
exit(1);
}
free(xi_mask.mask);
XSync(g->display, False);
}

bool qubes_daemon_xinput_plug__process_xevent__return_is_xinput_event(Ghandles * g, XEvent * xevent) {
if (!xinput_plugin_enabled) return false;

XGenericEventCookie *cookie = &xevent->xcookie;
if ( ! (XGetEventData(g->display, cookie) &&
cookie->type == GenericEvent &&
cookie->extension == g->xi_opcode)) return false;

XIEvent* xi_event = cookie->data; // from test_xi2.c in xinput cli utility

switch (xi_event->evtype) {
// ideally raw input events are better, but I'm relying on X server's built-in event filtering and routing feature here
case XI_KeyPress:
case XI_KeyRelease:
process_xinput_key(g, (XIDeviceEvent *)xi_event);
break;
case XI_FocusIn:
case XI_FocusOut:
process_xinput_focus(g, (XILeaveEvent *)xi_event);
break;
}
XFreeEventData(g->display, cookie);

return true;
}
5 changes: 5 additions & 0 deletions gui-daemon/xinput-plugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include <stdbool.h>
#include "xside.h"
void qubes_daemon_xinput_plug__init(Ghandles * g);
void qubes_daemon_xinput_plug__on_new_window(Ghandles * g, Window child_win);
bool qubes_daemon_xinput_plug__process_xevent__return_is_xinput_event(Ghandles * g, XEvent * xevent);
Loading