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

Create virtual input device to allow the use of kloak #194

Merged
merged 24 commits into from Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cd18ba3
create input device. if fails, will fall back to xdriver. Keyboard ev…
skyzzuu Aug 26, 2023
c3fe516
switched to storing the output_fd and createdInputDevice flag in the …
skyzzuu Aug 26, 2023
047edb5
added my actual email in the commit so the signature will be verified…
skyzzuu Aug 26, 2023
15b15f8
removed a comment from a function that doesn't exist anymore
skyzzuu Aug 26, 2023
1b0a80a
add some log_level checks on some of the fprintf statements I added
skyzzuu Aug 26, 2023
aabe25e
remove some uneeded whitespace and comments I added previously
skyzzuu Aug 26, 2023
b7ad65a
udev rules file that sets the group owner of the virtual input device…
skyzzuu Aug 27, 2023
c2f6757
virtual input device disabled by default. Can be enabled by enabling …
skyzzuu Aug 27, 2023
e6d5e59
Modifier sync will now happen all the time, regardless of whether vir…
skyzzuu Aug 27, 2023
f1637d2
removed lines setting bits for EV_REL and the REL_BITS. I'm not going…
skyzzuu Sep 8, 2023
d8ce3ab
rename output_fd to uinput_fd so it's clearer what the fd is
skyzzuu Sep 8, 2023
295464b
renamed the flag createdInputDevice to created_input_device to follow…
skyzzuu Sep 8, 2023
98d2dba
completely separated original handle_keypress function from the one d…
skyzzuu Sep 8, 2023
d30714d
changed group="user" to group="qubes" in udev rules file. Added lines…
skyzzuu Sep 9, 2023
b53b05e
made send_event static
skyzzuu Sep 9, 2023
1753606
exclude all BTN_* keys
skyzzuu Sep 9, 2023
bb5297d
removed some unneeded whitespace
skyzzuu Sep 9, 2023
399d0a7
Merge branch 'main' into main
skyzzuu Sep 9, 2023
ace84b3
an extra unused wait_fds[2] line made it in after the conflict was re…
skyzzuu Sep 9, 2023
f256db3
fixed caps lock. It was missing the line to flip its bit in last know…
skyzzuu Sep 10, 2023
becf0e7
remove debug line I added in while testing and forgot to remove in th…
skyzzuu Sep 10, 2023
827bb91
added check to see if modmap is null and moved XFreeModifierMap out o…
skyzzuu Sep 11, 2023
6bfefe9
remove incorrect comment
skyzzuu Sep 11, 2023
31c5988
comment explaining why capslock is excluded from key sending
skyzzuu Sep 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Expand Up @@ -219,6 +219,8 @@ endif
$(DESTDIR)/usr/lib/sysctl.d/30-qubes-gui-agent.conf
install -D -m 0644 appvm-scripts/lib/udev/rules.d/70-master-of-seat.rules \
$(DESTDIR)/$(UDEVRULESDIR)/70-master-of-seat.rules
install -D -m 0644 appvm-scripts/lib/udev/rules.d/90-qubes-virtual-input-device.rules \
$(DESTDIR)/$(UDEVRULESDIR)/90-qubes-virtual-input-device.rules
install -D appvm-scripts/usr/lib/qubes/qubes-gui-agent-pre.sh \
$(DESTDIR)/usr/lib/qubes/qubes-gui-agent-pre.sh
install -D appvm-scripts/usr/lib/qubes/qubes-keymap.sh \
Expand Down
@@ -0,0 +1 @@
ATTRS{name}=="Qubes Virtual Input Device", ACTION=="add", GROUP="qubes"
1 change: 1 addition & 0 deletions debian/qubes-gui-agent.install
Expand Up @@ -20,6 +20,7 @@ etc/xdg/fonts.conf
etc/xdg/xsettingsd
lib/systemd/system/qubes-gui-agent.service
lib/udev/rules.d/70-master-of-seat.rules
lib/udev/rules.d/90-qubes-virtual-input-device.rules
usr/bin/qubes-change-keyboard-layout
usr/bin/qubes-gui
usr/bin/qubes-gui-runuser
Expand Down
236 changes: 195 additions & 41 deletions gui-agent/vmside.c
Expand Up @@ -54,6 +54,13 @@
#include <libvchan.h>
#include <poll.h>


#include <linux/input.h>
#include <linux/uinput.h>
#include <time.h>



/* Time in milliseconds after which the clipboard data should be wiped */
#define CLIPBOARD_WIPE_TIME 60000

Expand Down Expand Up @@ -128,6 +135,9 @@ typedef struct {
uint32_t domid;
uint32_t protocol_version;
Time time;
int uinput_fd;
int created_input_device;
uint8_t last_known_modifier_states;
} Ghandles;

struct window_data {
Expand Down Expand Up @@ -284,6 +294,29 @@ static int compare_supported_cursors(const void *a, const void *b) {
((const struct supported_cursor *)b)->name);
}


static void send_event(Ghandles * g, const struct input_event *iev) {
int status = write(g->uinput_fd, iev, sizeof(struct input_event));
if ( status < 0 ) {
if (g->log_level > 0) {
fprintf(stderr, "write failed, falling back to xdriver. TYPE:%d CODE:%d VALUE: %d WRITE ERROR CODE: %d\n", iev->type, iev->code, iev->value, status);
}
g->created_input_device = 0;
}

// send syn
const struct input_event syn = {.type = EV_SYN, .code = 0, .value = 0};
status = write(g->uinput_fd, &(syn), sizeof(struct input_event));

if ( status < 0 ) {
if (g->log_level > 0) {
fprintf(stderr, "writing SYN failed, falling back to xdriver. WRITE ERROR CODE: %d\n", status);
}
g->created_input_device = 0;
}
}


static void send_wmname(Ghandles * g, XID window);
static void send_wmnormalhints(Ghandles * g, XID window, int ignore_fail);
static void send_wmclass(Ghandles * g, XID window, int ignore_fail);
Expand Down Expand Up @@ -1615,62 +1648,129 @@ static void handle_keypress(Ghandles * g, XID UNUSED(winid))
struct msg_keypress key;
XkbStateRec state;
read_data(g->vchan, (char *) &key, sizeof(key));
// sync modifiers state
if (XkbGetState(g->display, XkbUseCoreKbd, &state) != Success) {
if (g->log_level > 0)
fprintf(stderr, "failed to get modifier state\n");
state.mods = key.state;
}
if (!g->sync_all_modifiers) {
// ignore all but CapsLock
state.mods &= LockMask;
key.state &= LockMask;
}
if (state.mods != key.state) {
XModifierKeymap *modmap;
int mod_index;
int mod_mask;

if(!g->created_input_device) {
// sync modifiers state
if (XkbGetState(g->display, XkbUseCoreKbd, &state) != Success) {
if (g->log_level > 0)
fprintf(stderr, "failed to get modifier state\n");
state.mods = key.state;
}
if (!g->sync_all_modifiers) {
// ignore all but CapsLock
state.mods &= LockMask;
key.state &= LockMask;
}
if (state.mods != key.state) {
XModifierKeymap *modmap;
int mod_index;
int mod_mask;

modmap = XGetModifierMapping(g->display);
if (!modmap) {
if (g->log_level > 0)
fprintf(stderr, "failed to get modifier mapping\n");
} else {
// from X.h:
// #define ShiftMapIndex 0
// #define LockMapIndex 1
// #define ControlMapIndex 2
// #define Mod1MapIndex 3
// #define Mod2MapIndex 4
// #define Mod3MapIndex 5
// #define Mod4MapIndex 6
// #define Mod5MapIndex 7
for (mod_index = 0; mod_index < 8; mod_index++) {
if (modmap->modifiermap[mod_index*modmap->max_keypermod] == 0x00) {
if (g->log_level > 1)
fprintf(stderr, "ignoring disabled modifier %d\n", mod_index);
// no key set for this modifier, ignore
continue;
}
mod_mask = (1<<mod_index);
// special case for caps lock switch by press+release
if (mod_index == LockMapIndex) {
if ((state.mods & mod_mask) ^ (key.state & mod_mask)) {
feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 1);
feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 0);
}
} else {
if ((state.mods & mod_mask) && !(key.state & mod_mask))
feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 0);
else if (!(state.mods & mod_mask) && (key.state & mod_mask))
feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 1);
}
}
XFreeModifiermap(modmap);
}
}

feed_xdriver(g, 'K', key.keycode, key.type == KeyPress ? 1 : 0);
} else {
int mod_mask;
int mod_index;
struct input_event iev;
skyzzuu marked this conversation as resolved.
Show resolved Hide resolved
iev.type = EV_KEY;
XModifierKeymap *modmap;
modmap = XGetModifierMapping(g->display);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to handle also when this call fails (see above).


if (!modmap) {
if (g->log_level > 0)
fprintf(stderr, "failed to get modifier mapping\n");
if (g->log_level > 0)
fprintf(stderr, "failed to get modifier mapping\n");
} else {
// from X.h:
// #define ShiftMapIndex 0
// #define LockMapIndex 1
// #define ControlMapIndex 2
// #define Mod1MapIndex 3
// #define Mod2MapIndex 4
// #define Mod3MapIndex 5
// #define Mod4MapIndex 6
// #define Mod5MapIndex 7
for (mod_index = 0; mod_index < 8; mod_index++) {
for(mod_index = 0; mod_index < 8; mod_index++) {
if (modmap->modifiermap[mod_index*modmap->max_keypermod] == 0x00) {
if (g->log_level > 1)
fprintf(stderr, "ignoring disabled modifier %d\n", mod_index);
// no key set for this modifier, ignore
continue;
if (g->log_level > 1)
fprintf(stderr, "ignoring disabled modifier %d\n", mod_index);
// no key set for this modifier, ignore
continue;
}
mod_mask = (1<<mod_index);
// special case for caps lock switch by press+release
if (mod_index == LockMapIndex) {
if ((state.mods & mod_mask) ^ (key.state & mod_mask)) {
feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 1);
feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 0);
if ((g->last_known_modifier_states & mod_mask) ^ (key.state & mod_mask)) {
iev.code = modmap->modifiermap[mod_index*modmap->max_keypermod] - 8;
iev.value = 1;
send_event(g, &iev);
iev.value = 0;
send_event(g, &iev);
// update state for caps_lock
g->last_known_modifier_states ^= mod_mask;
}
} else {
if ((state.mods & mod_mask) && !(key.state & mod_mask))
feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 0);
else if (!(state.mods & mod_mask) && (key.state & mod_mask))
feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 1);
// last modifier state was pressed down, modifier has since been released
if ((g->last_known_modifier_states & mod_mask) && !(key.state & mod_mask)) {
iev.code = modmap->modifiermap[mod_index*modmap->max_keypermod] - 8;
iev.value = 0;
// send modifier release
send_event(g, &iev);
// update state for this modifier
g->last_known_modifier_states ^= mod_mask;
}

// last modifier state was up, modifier has since been pressed down
else if (!(g->last_known_modifier_states & mod_mask) && (key.state & mod_mask)) {
iev.code = modmap->modifiermap[mod_index*modmap->max_keypermod] - 8;
iev.value = 1;
// send modifier press
send_event(g, &iev);
// update state for this modifier
g->last_known_modifier_states ^= mod_mask;
}
}
}
XFreeModifiermap(modmap);
}

XFreeModifiermap(modmap);

// caps lock needs to be excluded to not send down, up, down or down, up, up on a caps lock sync instead of down, up
if(key.keycode-8 != KEY_CAPSLOCK) {
iev.code = key.keycode-8;
iev.value = (key.type == KeyPress ? 1 : 0);
send_event(g, &iev);
}

}

feed_xdriver(g, 'K', key.keycode, key.type == KeyPress ? 1 : 0);
}

static void handle_button(Ghandles * g, XID winid)
Expand Down Expand Up @@ -2268,6 +2368,60 @@ int main(int argc, char **argv)
int xfd;
Ghandles g;

g.created_input_device = access("/run/qubes-service/gui-agent-virtual-input-device", F_OK) == 0;

if(g.created_input_device) {
// open uinput, if it fails, falls back to xdriver to not break input to the qube
g.uinput_fd = open("/dev/uinput", O_WRONLY | O_NDELAY);
if(g.uinput_fd < 0) {
g.uinput_fd = open("/dev/input/uinput", O_WRONLY | O_NDELAY);

if(g.uinput_fd < 0) {
fprintf(stderr, "Couldn't open uinput, falling back to xdriver\n");
g.created_input_device = 0;
}
}
}

// input device creation
if(g.created_input_device) {

if (ioctl(g.uinput_fd, UI_SET_EVBIT, EV_SYN) < 0) {
fprintf(stderr, "error setting EVBIT for EV_SYN, falling back to xdriver\n");
g.created_input_device = 0;
}

if (ioctl(g.uinput_fd, UI_SET_EVBIT, EV_KEY) < 0) {
fprintf(stderr, "error setting EVBIT for EV_KEY, falling back to xdriver\n");
g.created_input_device = 0;
}

// set all keys
for(int i = 1; i < KEY_MAX; i++) {
if((i < BTN_MISC || i > BTN_GEAR_UP) && i != KEY_RESERVED && ioctl(g.uinput_fd, UI_SET_KEYBIT, i) < 0) {
fprintf(stderr, "Not able to set KEYBIT %d\n", i);
}
}

struct uinput_setup usetup;
memset(&usetup, 0, sizeof(usetup));
strcpy(usetup.name, "Qubes Virtual Input Device");

if(ioctl(g.uinput_fd, UI_DEV_SETUP, &usetup) < 0) {
fprintf(stderr, "Input device setup failed, falling back to xdriver\n");
g.created_input_device = 0;
} else {

if(ioctl(g.uinput_fd, UI_DEV_CREATE) < 0) {
fprintf(stderr, "Input device creation failed, falling back to xdriver\n");
g.created_input_device = 0;
}
}

g.last_known_modifier_states = 0;
}


parse_args(&g, argc, argv);

/* Clipboard wipe functionality is controlled by the
Expand Down
1 change: 1 addition & 0 deletions rpm_spec/gui-agent.spec.in
Expand Up @@ -252,6 +252,7 @@ rm -f %{name}-%{version}
%config /etc/sysconfig/desktop
%_unitdir/qubes-gui-agent.service
%_udevrulesdir/70-master-of-seat.rules
%_udevrulesdir/90-qubes-virtual-input-device.rules
%_sysctldir/30-qubes-gui-agent.conf
/usr/lib/qubes/gtk4-workarounds.sh
/usr/lib/qubes/qubes-gui-agent-pre.sh
Expand Down