From bf4793a8680790a2070055f84a3d1f9a4aab184a Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 6 Aug 2022 00:37:27 +0800 Subject: [PATCH 01/13] Add XI support Squashed commit of the following: commit 8fd4e7f2b1bbee48d5152fb73c9e3377dd50d2eb Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Fri Jun 17 03:08:10 2022 +0800 Add more error handling commit 403a952545ed20d1ad4879a318278adfef5f76eb Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sun Mar 13 13:34:04 2022 +0100 Fix double keypresses commit 110a6a9d3ee429d17b30b53bb2bc18060a704791 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Tue Mar 8 08:31:49 2022 +0800 Handle clipboard hotkeys properly commit cabf231d87108e5dab9d0921b40d46b240b1bf79 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sun Mar 6 03:31:58 2022 +0800 Use Focus events from XInput, dropping less keytrokes commit 2f9189402e7a46ce923390f0b93e7ceb0e2621fb Merge: 8ec6f01 954fb64 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat Mar 5 13:44:40 2022 +0800 Merge branch 'drop-repeat' of github.com:locriacyber/qubes-gui-daemon into drop-repeat commit 8ec6f0195e5b34583339887c9dd0820b89ef3e0f Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat Mar 5 13:34:13 2022 +0800 Use base Focus events commit 954fb647352adb116011b16e57e2ebc002d49713 Merge: 035be7f 14bbf21 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat Jun 18 04:42:33 2022 +0800 Merge branch 'master' into drop-repeat commit ee70785aaf5c4d6c0ed5d5dbf639b468955eff8a Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat Mar 5 13:23:32 2022 +0800 Pass Focus type commit 035be7f7cc281292aa480ab836d517feb7057381 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat Mar 5 13:34:13 2022 +0800 Use base Focus events commit a4a1488dd1a8bd1665b50b69f0d547fba192d157 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat Mar 5 13:19:36 2022 +0800 Don't ignore grab events commit 576fa94c862d3e537366635fc1306b17950b2b13 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat Mar 5 12:38:13 2022 +0800 Don't send evnts to unfocused window commit 5219d2ad9b504a2d9230f81f0dff116f598170fe Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat Feb 26 01:27:14 2022 +0800 Add XInput/libXi as build dependencies for debian & rpm commit 70da7d15704272cec8f2fee8e2d4069bb9019d90 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat Feb 26 01:24:28 2022 +0800 Update .gitignore commit 1bf52ea02c855233f1bf060308c4f4d37965c722 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Fri Feb 25 23:05:02 2022 +0800 Remove duplicate #include commit cb824aa2616ba50e0da4857c5167d725ae14ca00 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu Feb 24 21:37:34 2022 +0800 Free X resource as well commit 48323cdf84ab574ca6269c49d4fea48a65ac28d1 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu Feb 24 21:22:56 2022 +0800 Add libxi in Makefile commit 0611392d1fb206c65ba51e768fa851b2a6843baf Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu Feb 24 13:22:42 2022 +0100 Use XInput Key events instead of X basic events commit 6bddd0e04952a64d3bb4b482d07a7e1633da6ddc Merge: dc60cd4 60ee007 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu Feb 24 12:47:39 2022 +0100 Merge branch 'xinput' into drop-repeat commit dc60cd439a97bc1d8258eda9fbceffb5cf96db1b Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu Feb 24 19:34:20 2022 +0800 WIP check for XInput events commit 60ee007e74284a422cdbbe7ff71669d1cfd4f173 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu Feb 24 01:23:44 2022 +0800 Process xinput raw events too commit 86eb9043ad0120d48ce241ddccdcc87dff13c313 Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu Feb 24 01:11:41 2022 +0800 WIP: use XInput extension --- debian/control | 1 + gui-daemon/.gitignore | 2 + gui-daemon/Makefile | 2 +- gui-daemon/xside.c | 250 +++++++++++++++++++++--------------- gui-daemon/xside.h | 3 + rpm_spec/gui-daemon.spec.in | 1 + shmoverride/.gitignore | 2 +- 7 files changed, 154 insertions(+), 107 deletions(-) diff --git a/debian/control b/debian/control index 1cbc7700..1bee1f77 100644 --- a/debian/control +++ b/debian/control @@ -14,6 +14,7 @@ Build-Depends: libxrandr-dev, libxcb1-dev, libx11-xcb-dev, + libxi-dev, libconfig-dev, libpng-dev, libnotify-dev, diff --git a/gui-daemon/.gitignore b/gui-daemon/.gitignore index e84d2134..6e46ce29 100644 --- a/gui-daemon/.gitignore +++ b/gui-daemon/.gitignore @@ -1 +1,3 @@ qubes_guid +qubes-guid +qubes-guid.1 diff --git a/gui-daemon/Makefile b/gui-daemon/Makefile index 9b4b4c25..ef00b809 100644 --- a/gui-daemon/Makefile +++ b/gui-daemon/Makefile @@ -22,7 +22,7 @@ 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 +pkgs := x11 xext x11-xcb xcb glib-2.0 xi $(VCHAN_PKG) libpng libnotify libconfig objs := xside.o png.o trayicon.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 \ diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index 91669932..2c58abf6 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include #include #include @@ -272,6 +274,14 @@ int x11_error_handler(Display * dpy, XErrorEvent * ev) * handled in handle_mfndump/handle_window_dump */ return 0; } + + char error_msg[1024]; + XGetErrorText(ev->display, ev->error_code, error_msg, sizeof(error_msg)); + int now = (int) time(NULL); // truncate + fprintf(stderr, "[%d] Encountered X Error:\n", now); + fprintf(stderr, error_msg); + + #ifdef MAKE_X11_ERRORS_FATAL /* The exit(1) below will call release_all_mapped_mfns (registerd with * atexit(3)), which would try to release window images with XShmDetach. We @@ -374,10 +384,33 @@ static Window mkwindow(Ghandles * g, struct windowdata *vm_window) (const unsigned char *)&g->time_win, 1); (void) XSelectInput(g->display, child_win, - ExposureMask | KeyPressMask | KeyReleaseMask | + ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | - FocusChangeMask | StructureNotifyMask | PropertyChangeMask); + StructureNotifyMask | PropertyChangeMask); + + // 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); + + XSetWMProtocols(g->display, child_win, &g->wmDeleteMessage, 1); if (g->icon_data) { XChangeProperty(g->display, child_win, g->net_wm_icon, XA_CARDINAL, 32, @@ -656,6 +689,10 @@ static void mkghandles(Ghandles * g) if (!XQueryExtension(g->display, "MIT-SHM", &g->shm_major_opcode, &ev_base, &err_base)) fprintf(stderr, "MIT-SHM X extension missing!\n"); + 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); + } /* get the work area */ XSelectInput(g->display, g->root_win, PropertyChangeMask); update_work_area(g); @@ -1250,7 +1287,7 @@ static void handle_cursor(Ghandles *g, struct windowdata *vm_window) /* check and handle guid-special keys * currently only for inter-vm clipboard copy */ -static int is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_winid) +static int is_special_keypress(Ghandles * g, const XIDeviceEvent * ev, XID remote_winid) { struct msg_hdr hdr; char *data; @@ -1258,9 +1295,9 @@ static int is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_wi Time clipboard_file_xevent_time; /* copy */ - if (((int)ev->state & SPECIAL_KEYS_MASK) == g->copy_seq_mask - && ev->keycode == XKeysymToKeycode(g->display, g->copy_seq_key)) { - if (ev->type != KeyPress) + if (((int)ev->mods.effective & SPECIAL_KEYS_MASK) == g->copy_seq_mask + && ev->detail == XKeysymToKeycode(g->display, g->copy_seq_key)) { + if (ev->evtype != KeyPress) return 1; g->clipboard_xevent_time = ev->time; if (g->qrexec_clipboard) { @@ -1280,9 +1317,9 @@ static int is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_wi } /* paste */ - if (((int)ev->state & SPECIAL_KEYS_MASK) == g->paste_seq_mask - && ev->keycode == XKeysymToKeycode(g->display, g->paste_seq_key)) { - if (ev->type != KeyPress) + if (((int)ev->mods.effective & SPECIAL_KEYS_MASK) == g->paste_seq_mask + && ev->detail == XKeysymToKeycode(g->display, g->paste_seq_key)) { + if (ev->evtype != KeyPress) return 1; inter_appviewer_lock(g, 1); clipboard_file_xevent_time = get_clipboard_file_xevent_timestamp(); @@ -1339,22 +1376,25 @@ static void update_wm_user_time(Ghandles *const g, const Window window, 1); } -/* handle local Xserver event: XKeyEvent +/* handle local XInput event * send it to relevant window in VM */ -static void process_xevent_keypress(Ghandles * g, const XKeyEvent * ev) +static void process_xievent_keypress(Ghandles * g, const XIDeviceEvent * ev) { struct msg_hdr hdr; struct msg_keypress k; - CHECK_NONMANAGED_WINDOW(g, ev->window); - update_wm_user_time(g, ev->window, ev->time); + 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(g, ev, vm_window->remote_winid)) return; - k.type = ev->type; - k.x = ev->x; - k.y = ev->y; - k.state = ev->state; - k.keycode = ev->keycode; + k.type = ev->evtype; // ev->type is always Generic Event + k.x = ev->event_x; + k.y = ev->event_y; + k.state = ev->mods.effective; + k.keycode = ev->detail; hdr.type = MSG_KEYPRESS; hdr.window = vm_window->remote_winid; write_message(g->vchan, hdr, k); @@ -1392,7 +1432,6 @@ static void process_xevent_button(Ghandles * g, const XButtonEvent * ev) update_wm_user_time(g, ev->window, ev->time); k.type = ev->type; - k.x = ev->x; k.y = ev->y; k.state = ev->state; @@ -1880,6 +1919,20 @@ static void handle_configure_from_vm(Ghandles * g, struct windowdata *vm_window) } } +static void send_keymap_notify(Ghandles * g) +{ + struct msg_hdr hdr; + char keys[32]; + int err = XQueryKeymap(g->display, keys); + if (err) { + fprintf(stderr, "XQueryKeymap failed: %d.\n", err); + return; // non fatal + } + hdr.type = MSG_KEYMAP_NOTIFY; + hdr.window = 0; + write_message(g->vchan, hdr, keys); +} + /* handle local Xserver event: EnterNotify, LeaveNotify * send it to VM, but alwo we use it to fix docked * window position */ @@ -1890,11 +1943,7 @@ static void process_xevent_crossing(Ghandles * g, const XCrossingEvent * ev) CHECK_NONMANAGED_WINDOW(g, ev->window); if (ev->type == EnterNotify) { - char keys[32]; - XQueryKeymap(g->display, keys); - hdr.type = MSG_KEYMAP_NOTIFY; - hdr.window = 0; - write_message(g->vchan, hdr, keys); + send_keymap_notify(g); } /* move tray to correct position in VM */ if (vm_window->is_docked && @@ -1935,35 +1984,20 @@ static void process_xevent_motion(Ghandles * g, const XMotionEvent * ev) /* handle local Xserver event: FocusIn, FocusOut * send to relevant window in VM */ -static void process_xevent_focus(Ghandles * g, const XFocusChangeEvent * ev) +static void process_xievent_focus(Ghandles * g, const XILeaveEvent * ev) { struct msg_hdr hdr; struct msg_focus k; - CHECK_NONMANAGED_WINDOW(g, ev->window); + CHECK_NONMANAGED_WINDOW(g, ev->event); + update_wm_user_time(g, ev->event, ev->time); - /* Ignore everything other than normal, non-temporary focus change. In - * practice it ignores NotifyGrab and NotifyUngrab. VM does not have any - * way to grab focus in dom0, so it shouldn't care about those events. Grab - * is used by window managers during task switching (either classic task - * switcher, or KDE "present windows" feature). - */ - if (ev->mode != NotifyNormal && ev->mode != NotifyWhileGrabbed) - return; - - if (ev->type == FocusIn) { - char keys[32]; - XQueryKeymap(g->display, keys); - hdr.type = MSG_KEYMAP_NOTIFY; - hdr.window = 0; - write_message(g->vchan, hdr, keys); + if (ev->type == XI_FocusIn) { + send_keymap_notify(g); } hdr.type = MSG_FOCUS; hdr.window = vm_window->remote_winid; - k.type = ev->type; - /* override NotifyWhileGrabbed with NotifyNormal b/c VM shouldn't care - * about window manager details during focus switching - */ - k.mode = NotifyNormal; + k.type = ev->evtype; + k.mode = ev->mode; k.detail = ev->detail; write_message(g->vchan, hdr, k); } @@ -2313,11 +2347,7 @@ static void process_xevent_xembed(Ghandles * g, const XClientMessageEvent * ev) } else if (ev->data.l[1] == XEMBED_FOCUS_IN) { struct msg_hdr hdr; struct msg_focus k; - char keys[32]; - XQueryKeymap(g->display, keys); - hdr.type = MSG_KEYMAP_NOTIFY; - hdr.window = 0; - write_message(g->vchan, hdr, keys); + send_keymap_notify(g); hdr.type = MSG_FOCUS; hdr.window = vm_window->remote_winid; k.type = FocusIn; @@ -2332,62 +2362,72 @@ static void process_xevent_xembed(Ghandles * g, const XClientMessageEvent * ev) static void process_xevent(Ghandles * g) { XEvent event_buffer; + XGenericEventCookie *cookie = &event_buffer.xcookie; XNextEvent(g->display, &event_buffer); - switch (event_buffer.type) { - case KeyPress: - case KeyRelease: - process_xevent_keypress(g, (XKeyEvent *) & event_buffer); - break; - case ReparentNotify: - process_xevent_reparent(g, (XReparentEvent *) &event_buffer); - break; - case ConfigureNotify: - process_xevent_configure(g, (XConfigureEvent *) & - event_buffer); - break; - case ButtonPress: - case ButtonRelease: - process_xevent_button(g, (XButtonEvent *) & event_buffer); - break; - case MotionNotify: - process_xevent_motion(g, (XMotionEvent *) & event_buffer); - break; - case EnterNotify: - case LeaveNotify: - process_xevent_crossing(g, - (XCrossingEvent *) & event_buffer); - break; - case FocusIn: - case FocusOut: - process_xevent_focus(g, - (XFocusChangeEvent *) & event_buffer); - break; - case Expose: - process_xevent_expose(g, (XExposeEvent *) & event_buffer); - break; - case MapNotify: - process_xevent_mapnotify(g, (XMapEvent *) & event_buffer); - break; - case PropertyNotify: - process_xevent_propertynotify(g, (XPropertyEvent *) & event_buffer); - break; - case ClientMessage: -// fprintf(stderr, "xclient, atom=%s\n", -// XGetAtomName(g->display, -// event_buffer.xclient.message_type)); - if (event_buffer.xclient.message_type == g->xembed_message) { - process_xevent_xembed(g, (XClientMessageEvent *) & - event_buffer); - } else if ((Atom)event_buffer.xclient.data.l[0] == - g->wmDeleteMessage) { - if (g->log_level > 0) - fprintf(stderr, "close for 0x%x\n", - (int) event_buffer.xclient.window); - process_xevent_close(g, - event_buffer.xclient.window); + if (XGetEventData(g->display, cookie) && + cookie->type == GenericEvent && + cookie->extension == g->xi_opcode) { + 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_xievent_keypress(g, (XIDeviceEvent *)xi_event); + break; + case XI_FocusIn: + case XI_FocusOut: + process_xievent_focus(g, (XILeaveEvent *)xi_event); + break; + } + XFreeEventData(g->display, cookie); + } else { + switch (event_buffer.type) { + case ReparentNotify: + process_xevent_reparent(g, (XReparentEvent *) &event_buffer); + break; + case ConfigureNotify: + process_xevent_configure(g, (XConfigureEvent *) & + event_buffer); + break; + case ButtonPress: + case ButtonRelease: + process_xevent_button(g, (XButtonEvent *) & event_buffer); + break; + case MotionNotify: + process_xevent_motion(g, (XMotionEvent *) & event_buffer); + break; + case EnterNotify: + case LeaveNotify: + process_xevent_crossing(g, + (XCrossingEvent *) & event_buffer); + break; + case Expose: + process_xevent_expose(g, (XExposeEvent *) & event_buffer); + break; + case MapNotify: + process_xevent_mapnotify(g, (XMapEvent *) & event_buffer); + break; + case PropertyNotify: + process_xevent_propertynotify(g, (XPropertyEvent *) & event_buffer); + break; + case ClientMessage: + // fprintf(stderr, "xclient, atom=%s\n", + // XGetAtomName(g->display, + // event_buffer.xclient.message_type)); + if (event_buffer.xclient.message_type == g->xembed_message) { + process_xevent_xembed(g, (XClientMessageEvent *) & + event_buffer); + } else if ((Atom)event_buffer.xclient.data.l[0] == + g->wmDeleteMessage) { + if (g->log_level > 0) + fprintf(stderr, "close for 0x%x\n", + (int) event_buffer.xclient.window); + process_xevent_close(g, + event_buffer.xclient.window); + } + break; } - break; - default:; } } diff --git a/gui-daemon/xside.h b/gui-daemon/xside.h index 9ec281d8..8f391e79 100644 --- a/gui-daemon/xside.h +++ b/gui-daemon/xside.h @@ -140,6 +140,7 @@ struct extra_prop { }; /* global variables + * One for one VM * keep them in this struct for readability */ struct _global_handles { @@ -172,6 +173,7 @@ struct _global_handles { Atom wm_user_time_window; /* Atom: _NET_WM_USER_TIME_WINDOW */ Atom wm_user_time; /* Atom: _NET_WM_USER_TIME */ int shm_major_opcode; /* MIT-SHM extension opcode */ + int xi_opcode; /* XInput opcode, for repeat flags and such */ /* shared memory handling */ struct shm_args_hdr *shm_args; /* shared memory with Xorg */ uint32_t cmd_shmid; /* shared memory id - received from shmoverride.so through shm.id.$DISPLAY file */ @@ -200,6 +202,7 @@ struct _global_handles { int clipboard_requested; /* if clippoard content was requested by dom0 */ Time clipboard_xevent_time; /* timestamp of keypress which triggered last copy/paste */ Window time_win; /* Window to set _NET_WM_USER_TIME on */ + /* signal was caught */ int volatile reload_requested; pid_t pulseaudio_pid; diff --git a/rpm_spec/gui-daemon.spec.in b/rpm_spec/gui-daemon.spec.in index b644364f..627b79bc 100644 --- a/rpm_spec/gui-daemon.spec.in +++ b/rpm_spec/gui-daemon.spec.in @@ -49,6 +49,7 @@ BuildRequires: pkgconfig(x11-xcb) BuildRequires: pkgconfig(xcb) BuildRequires: libXext-devel BuildRequires: libXrandr-devel +BuildRequires: libXi-devel BuildRequires: libconfig-devel BuildRequires: libpng-devel BuildRequires: libnotify-devel diff --git a/shmoverride/.gitignore b/shmoverride/.gitignore index 979bacea..65c0988c 100644 --- a/shmoverride/.gitignore +++ b/shmoverride/.gitignore @@ -1,3 +1,3 @@ -X_wrapper_qubes +X-wrapper-qubes shmoverride.so From c336ff47d1010b3ef1259c80edd7a159aaf22e09 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 6 Aug 2022 00:42:55 +0800 Subject: [PATCH 02/13] revert xside.c and turn diff into patch --- gui-daemon/xinput.patch | 267 ++++++++++++++++++++++++++++++++++++++++ gui-daemon/xside.c | 250 ++++++++++++++++--------------------- 2 files changed, 372 insertions(+), 145 deletions(-) create mode 100644 gui-daemon/xinput.patch diff --git a/gui-daemon/xinput.patch b/gui-daemon/xinput.patch new file mode 100644 index 00000000..87b0a17c --- /dev/null +++ b/gui-daemon/xinput.patch @@ -0,0 +1,267 @@ + gui-daemon/xside.c | 250 +++++++++++++++++++++++++++++++---------------------- + 1 file changed, 145 insertions(+), 105 deletions(-) + +gui-daemon/xside.c --- 1/14 --- C +47 47 #include +48 48 #include +49 49 #include +50 50 #include +.. 51 #include +51 52 #include +52 53 #include +53 54 #include +54 55 #include + +gui-daemon/xside.c --- 2/14 --- C +273 275 return 0; +274 276 } +... 277 +... 278 char error_msg[1024]; +... 279 XGetErrorText(ev->display, ev->error_code, error_msg, sizeof(error_msg)); +... 280 int now = (int) time(NULL); // truncate +... 281 fprintf(stderr, "[%d] Encountered X Error:\n", now); +... 282 fprintf(stderr, error_msg); +... 283 +... 284 +275 285 #ifdef MAKE_X11_ERRORS_FATAL + +gui-daemon/xside.c --- 3/14 --- C +374 (const unsigned char *)&g->time_win, 384 (const unsigned char *)&g->time_win, +375 1); 385 1); +376 (void) XSelectInput(g->display, child_win, 386 (void) XSelectInput(g->display, child_win, +377 ExposureMask | KeyPressMask | KeyReleaseMask | 387 ExposureMask | +378 ButtonPressMask | ButtonReleaseMask | 388 ButtonPressMask | ButtonReleaseMask | +379 PointerMotionMask | EnterWindowMask | LeaveWindowMask | 389 PointerMotionMask | EnterWindowMask | LeaveWindowMask | +380 FocusChangeMask | StructureNotifyMask | PropertyChangeMask); 390 StructureNotifyMask | PropertyChangeMask); +...  391 +...  392 // select xinput events +...  393 XIEventMask xi_mask; +...  394 xi_mask.deviceid = XIAllMasterDevices; // https://stackoverflow.com/questions/440 +...  ... 95001/getting-double-rawkeypress-events-using-xinput2 +...  395 xi_mask.mask_len = XIMaskLen(XI_LASTEVENT); +...  396 if (!(xi_mask.mask = calloc(xi_mask.mask_len, sizeof(char)))) { +...  397 fputs("Out of memory!\n", stderr); +...  398 exit(1); +...  399 } +...  400 XISetMask(xi_mask.mask, XI_KeyPress); +...  401 XISetMask(xi_mask.mask, XI_KeyRelease); +...  402 XISetMask(xi_mask.mask, XI_FocusIn); +...  403 XISetMask(xi_mask.mask, XI_FocusOut); +...  404 +...  405 int err = XISelectEvents(g->display, child_win, &xi_mask, 1); +...  406 if (err) { +...  407 fprintf(stderr, "Failed to subscribe to XI events. ErrCode: %d\n", err); +...  408 exit(1); +...  409 } +...  410 free(xi_mask.mask); +...  411 XSync(g->display, False); +...  412 +...  413 +381 XSetWMProtocols(g->display, child_win, &g->wmDeleteMessage, 1); 414 XSetWMProtocols(g->display, child_win, &g->wmDeleteMessage, 1); + +gui-daemon/xside.c --- 4/14 --- C +656 689 if (!XQueryExtension(g->display, "MIT-SHM", +657 690 &g->shm_major_opcode, &ev_base, &err_base)) +658 691 fprintf(stderr, "MIT-SHM X extension missing!\n"); +... 692 if (!XQueryExtension(g->display, "XInputExtension", &g->xi_opcode, &ev_base, &err_base)) { +... 693 fprintf(stderr, "X Input extension not available. Key press events not available. Upgrade your X11 server now.\n"); +... 694 exit(1); +... 695 } +659 696 /* get the work area */ +660 697 XSelectInput(g->display, g->root_win, PropertyChangeMask); +661 698 update_work_area(g); + +gui-daemon/xside.c --- 5/14 --- C +1250 /* check and handle guid-special keys 1287 /* check and handle guid-special keys +1251 * currently only for inter-vm clipboard copy 1288 * currently only for inter-vm clipboard copy +1252 */ 1289 */ +1253 static int is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_winid) 1290 static int is_special_keypress(Ghandles * g, const XIDeviceEvent * ev, XID remote_wi +....  .... nid) +1254 { 1291 { +1255 struct msg_hdr hdr; 1292 struct msg_hdr hdr; +1256 char *data; 1293 char *data; +1257 int len; 1294 int len; +1258 Time clipboard_file_xevent_time; 1295 Time clipboard_file_xevent_time; +1259 1296 +1260 /* copy */ 1297 /* copy */ +1261 if (((int)ev->state & SPECIAL_KEYS_MASK) == g->copy_seq_mask 1298 if (((int)ev->mods.effective & SPECIAL_KEYS_MASK) == g->copy_seq_mask +1262 && ev->keycode == XKeysymToKeycode(g->display, g->copy_seq_key)) { 1299 && ev->detail == XKeysymToKeycode(g->display, g->copy_seq_key)) { +1263 if (ev->type != KeyPress) 1300 if (ev->evtype != KeyPress) +1264 return 1; 1301 return 1; +1265 g->clipboard_xevent_time = ev->time; 1302 g->clipboard_xevent_time = ev->time; +1266 if (g->qrexec_clipboard) { 1303 if (g->qrexec_clipboard) { + +gui-daemon/xside.c --- 6/14 --- C +1280 } 1317 } +1281 1318 +1282 /* paste */ 1319 /* paste */ +1283 if (((int)ev->state & SPECIAL_KEYS_MASK) == g->paste_seq_mask 1320 if (((int)ev->mods.effective & SPECIAL_KEYS_MASK) == g->paste_seq_mask +1284 && ev->keycode == XKeysymToKeycode(g->display, g->paste_seq_key)) { 1321 && ev->detail == XKeysymToKeycode(g->display, g->paste_seq_key)) { +1285 if (ev->type != KeyPress) 1322 if (ev->evtype != KeyPress) +1286 return 1; 1323 return 1; +1287 inter_appviewer_lock(g, 1); 1324 inter_appviewer_lock(g, 1); +1288 clipboard_file_xevent_time = get_clipboard_file_xevent_timestamp(); 1325 clipboard_file_xevent_time = get_clipboard_file_xevent_timestamp(); + +gui-daemon/xside.c --- 7/14 --- C +1339 1); 1376 1); +1340 } 1377 } +1341 1378 +1342 /* handle local Xserver event: XKeyEvent 1379 /* handle local XInput event +1343 * send it to relevant window in VM 1380 * send it to relevant window in VM +1344 */ 1381 */ +1345 static void process_xevent_keypress(Ghandles * g, const XKeyEvent * ev) 1382 static void process_xievent_keypress(Ghandles * g, const XIDeviceEvent * ev) +1346 { 1383 { +1347 struct msg_hdr hdr; 1384 struct msg_hdr hdr; +1348 struct msg_keypress k; 1385 struct msg_keypress k; +1349 CHECK_NONMANAGED_WINDOW(g, ev->window); 1386 CHECK_NONMANAGED_WINDOW(g, ev->event); +....  1387 // yes, ev->event is the window number +1350 update_wm_user_time(g, ev->window, ev->time); 1388 update_wm_user_time(g, ev->event, ev->time); +....  1389 if (ev->flags & XIKeyRepeat) +....  1390 return; // don't send key repeat events +1351 if (is_special_keypress(g, ev, vm_window->remote_winid)) 1391 if (is_special_keypress(g, ev, vm_window->remote_winid)) +1352 return; 1392 return; +1353 k.type = ev->type; 1393 k.type = ev->evtype; // ev->type is always Generic Event +1354 k.x = ev->x; 1394 k.x = ev->event_x; +1355 k.y = ev->y; 1395 k.y = ev->event_y; +1356 k.state = ev->state; 1396 k.state = ev->mods.effective; +1357 k.keycode = ev->keycode; 1397 k.keycode = ev->detail; +1358 hdr.type = MSG_KEYPRESS; 1398 hdr.type = MSG_KEYPRESS; +1359 hdr.window = vm_window->remote_winid; 1399 hdr.window = vm_window->remote_winid; +1360 write_message(g->vchan, hdr, k); 1400 write_message(g->vchan, hdr, k); + +gui-daemon/xside.c --- 8/14 --- C +1880 1919 } +1881 1920 } +.... 1921 +.... 1922 static void send_keymap_notify(Ghandles * g) +.... 1923 { +.... 1924 struct msg_hdr hdr; +.... 1925 char keys[32]; +.... 1926 int err = XQueryKeymap(g->display, keys); +.... 1927 if (err) { +.... 1928 fprintf(stderr, "XQueryKeymap failed: %d.\n", err); +.... 1929 return; // non fatal +.... 1930 } +.... 1931 hdr.type = MSG_KEYMAP_NOTIFY; +.... 1932 hdr.window = 0; +.... 1933 write_message(g->vchan, hdr, keys); +.... 1934 } +1882 1935 +1883 1936 /* handle local Xserver event: EnterNotify, LeaveNotify +1884 1937 * send it to VM, but alwo we use it to fix docked + +gui-daemon/xside.c --- 9/14 --- C +1890 CHECK_NONMANAGED_WINDOW(g, ev->window); 1943 CHECK_NONMANAGED_WINDOW(g, ev->window); +1891 1944 +1892 if (ev->type == EnterNotify) { 1945 if (ev->type == EnterNotify) { +1893 char keys[32]; ....  +1894 XQueryKeymap(g->display, keys); 1946 send_keymap_notify(g); +1895 hdr.type = MSG_KEYMAP_NOTIFY; ....  +1896 hdr.window = 0; ....  +1897 write_message(g->vchan, hdr, keys); ....  +1898 } 1947 } +1899 /* move tray to correct position in VM */ 1948 /* move tray to correct position in VM */ +1900 if (vm_window->is_docked && 1949 if (vm_window->is_docked && + +gui-daemon/xside.c --- 10/14 --- C +1935 1984 +1936 /* handle local Xserver event: FocusIn, FocusOut 1985 /* handle local Xserver event: FocusIn, FocusOut +1937 * send to relevant window in VM */ 1986 * send to relevant window in VM */ +1938 static void process_xevent_focus(Ghandles * g, const XFocusChangeEvent * ev) 1987 static void process_xievent_focus(Ghandles * g, const XILeaveEvent * ev) +1939 { 1988 { +1940 struct msg_hdr hdr; 1989 struct msg_hdr hdr; +1941 struct msg_focus k; 1990 struct msg_focus k; +1942 CHECK_NONMANAGED_WINDOW(g, ev->window); 1991 CHECK_NONMANAGED_WINDOW(g, ev->event); +1943 ....  +1944 /* Ignore everything other than normal, non-temporary focus change. In ....  +1945 * practice it ignores NotifyGrab and NotifyUngrab. VM does not have any ....  +1946 * way to grab focus in dom0, so it shouldn't care about those events. Grab ....  +1947 * is used by window managers during task switching (either classic task ....  +1948 * switcher, or KDE "present windows" feature). ....  +1949 */ ....  +1950 if (ev->mode != NotifyNormal && ev->mode != NotifyWhileGrabbed) 1992 update_wm_user_time(g, ev->event, ev->time); +1951 return; ....  +1952 1993 +1953 if (ev->type == FocusIn) { 1994 if (ev->type == XI_FocusIn) { +1954 char keys[32]; ....  +1955 XQueryKeymap(g->display, keys); 1995 send_keymap_notify(g); +1956 hdr.type = MSG_KEYMAP_NOTIFY; ....  +1957 hdr.window = 0; ....  +1958 write_message(g->vchan, hdr, keys); ....  +1959 } 1996 } +1960 hdr.type = MSG_FOCUS; 1997 hdr.type = MSG_FOCUS; +1961 hdr.window = vm_window->remote_winid; 1998 hdr.window = vm_window->remote_winid; +1962 k.type = ev->type; 1999 k.type = ev->evtype; +1963 /* override NotifyWhileGrabbed with NotifyNormal b/c VM shouldn't care ....  +1964 * about window manager details during focus switching ....  +1965 */ ....  +1966 k.mode = NotifyNormal; 2000 k.mode = ev->mode; +1967 k.detail = ev->detail; 2001 k.detail = ev->detail; +1968 write_message(g->vchan, hdr, k); 2002 write_message(g->vchan, hdr, k); +1969 } 2003 } + +gui-daemon/xside.c --- 11/14 --- C +2313 } else if (ev->data.l[1] == XEMBED_FOCUS_IN) { 2347 } else if (ev->data.l[1] == XEMBED_FOCUS_IN) { +2314 struct msg_hdr hdr; 2348 struct msg_hdr hdr; +2315 struct msg_focus k; 2349 struct msg_focus k; +2316 char keys[32]; ....  +2317 XQueryKeymap(g->display, keys); 2350 send_keymap_notify(g); +2318 hdr.type = MSG_KEYMAP_NOTIFY; ....  +2319 hdr.window = 0; ....  +2320 write_message(g->vchan, hdr, keys); ....  +2321 hdr.type = MSG_FOCUS; 2351 hdr.type = MSG_FOCUS; +2322 hdr.window = vm_window->remote_winid; 2352 hdr.window = vm_window->remote_winid; +2323 k.type = FocusIn; 2353 k.type = FocusIn; + +gui-daemon/xside.c --- 12/14 --- C +2332 static void process_xevent(Ghandles * g) 2362 static void process_xevent(Ghandles * g) +2333 { 2363 { +2334 XEvent event_buffer; 2364 XEvent event_buffer; +....  2365 XGenericEventCookie *cookie = &event_buffer.xcookie; +2335 XNextEvent(g->display, &event_buffer); 2366 XNextEvent(g->display, &event_buffer); +2336 switch (event_buffer.type) { 2367 if (XGetEventData(g->display, cookie) && +2337 case KeyPress: 2368 cookie->type == GenericEvent && +2338 case KeyRelease: 2369 cookie->extension == g->xi_opcode) { +2339 process_xevent_keypress(g, (XKeyEvent *) & event_buffer); 2370 XIEvent* xi_event = cookie->data; // from test_xi2.c in xinput cli utility +2340 break; 2371 +....  2372 switch (xi_event->evtype) { +....  2373 // ideally raw input events are better, but I'm relying on X server's built- +....  .... in event filtering and routing feature here +....  2374 case XI_KeyPress: +....  2375 case XI_KeyRelease: +....  2376 process_xievent_keypress(g, (XIDeviceEvent *)xi_event); +....  2377 break; +....  2378 case XI_FocusIn: +....  2379 case XI_FocusOut: +....  2380 process_xievent_focus(g, (XILeaveEvent *)xi_event); +....  2381 break; +....  2382 } +....  2383 XFreeEventData(g->display, cookie); +....  2384 } else { +....  2385 switch (event_buffer.type) { +2341 case ReparentNotify: 2386 case ReparentNotify: +2342 process_xevent_reparent(g, (XReparentEvent *) &event_buffer); 2387 process_xevent_reparent(g, (XReparentEvent *) &event_buffer); +2343 break; 2388 break; + +gui-daemon/xside.c --- 13/14 --- C +2357 2402 process_xevent_crossing(g, +2358 2403 (XCrossingEvent *) & event_buffer); +2359 2404 break; +2360 ....  case FocusIn: +2361 ....  case FocusOut: +2362 ....  process_xevent_focus(g, +2363 ....  (XFocusChangeEvent *) & event_buffer); +2364 ....  break; +2365 2405 case Expose: +2366 2406 process_xevent_expose(g, (XExposeEvent *) & event_buffer); +2367 2407 break; + +gui-daemon/xside.c --- 14/14 --- C +2387 event_buffer.xclient.window); 2427 event_buffer.xclient.window); +2388 } 2428 } +2389 break; 2429 break; +2390 default:; 2430 } +2391 } 2431 } +2392 } 2432 } +2393 2433 + diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index 2c58abf6..91669932 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -47,8 +47,6 @@ #include #include #include -#include -#include #include #include #include @@ -274,14 +272,6 @@ int x11_error_handler(Display * dpy, XErrorEvent * ev) * handled in handle_mfndump/handle_window_dump */ return 0; } - - char error_msg[1024]; - XGetErrorText(ev->display, ev->error_code, error_msg, sizeof(error_msg)); - int now = (int) time(NULL); // truncate - fprintf(stderr, "[%d] Encountered X Error:\n", now); - fprintf(stderr, error_msg); - - #ifdef MAKE_X11_ERRORS_FATAL /* The exit(1) below will call release_all_mapped_mfns (registerd with * atexit(3)), which would try to release window images with XShmDetach. We @@ -384,33 +374,10 @@ static Window mkwindow(Ghandles * g, struct windowdata *vm_window) (const unsigned char *)&g->time_win, 1); (void) XSelectInput(g->display, child_win, - ExposureMask | + ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | - StructureNotifyMask | PropertyChangeMask); - - // 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); - - + FocusChangeMask | StructureNotifyMask | PropertyChangeMask); XSetWMProtocols(g->display, child_win, &g->wmDeleteMessage, 1); if (g->icon_data) { XChangeProperty(g->display, child_win, g->net_wm_icon, XA_CARDINAL, 32, @@ -689,10 +656,6 @@ static void mkghandles(Ghandles * g) if (!XQueryExtension(g->display, "MIT-SHM", &g->shm_major_opcode, &ev_base, &err_base)) fprintf(stderr, "MIT-SHM X extension missing!\n"); - 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); - } /* get the work area */ XSelectInput(g->display, g->root_win, PropertyChangeMask); update_work_area(g); @@ -1287,7 +1250,7 @@ static void handle_cursor(Ghandles *g, struct windowdata *vm_window) /* check and handle guid-special keys * currently only for inter-vm clipboard copy */ -static int is_special_keypress(Ghandles * g, const XIDeviceEvent * ev, XID remote_winid) +static int is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_winid) { struct msg_hdr hdr; char *data; @@ -1295,9 +1258,9 @@ static int is_special_keypress(Ghandles * g, const XIDeviceEvent * ev, XID remot Time clipboard_file_xevent_time; /* copy */ - if (((int)ev->mods.effective & SPECIAL_KEYS_MASK) == g->copy_seq_mask - && ev->detail == XKeysymToKeycode(g->display, g->copy_seq_key)) { - if (ev->evtype != KeyPress) + if (((int)ev->state & SPECIAL_KEYS_MASK) == g->copy_seq_mask + && ev->keycode == XKeysymToKeycode(g->display, g->copy_seq_key)) { + if (ev->type != KeyPress) return 1; g->clipboard_xevent_time = ev->time; if (g->qrexec_clipboard) { @@ -1317,9 +1280,9 @@ static int is_special_keypress(Ghandles * g, const XIDeviceEvent * ev, XID remot } /* paste */ - if (((int)ev->mods.effective & SPECIAL_KEYS_MASK) == g->paste_seq_mask - && ev->detail == XKeysymToKeycode(g->display, g->paste_seq_key)) { - if (ev->evtype != KeyPress) + if (((int)ev->state & SPECIAL_KEYS_MASK) == g->paste_seq_mask + && ev->keycode == XKeysymToKeycode(g->display, g->paste_seq_key)) { + if (ev->type != KeyPress) return 1; inter_appviewer_lock(g, 1); clipboard_file_xevent_time = get_clipboard_file_xevent_timestamp(); @@ -1376,25 +1339,22 @@ static void update_wm_user_time(Ghandles *const g, const Window window, 1); } -/* handle local XInput event +/* handle local Xserver event: XKeyEvent * send it to relevant window in VM */ -static void process_xievent_keypress(Ghandles * g, const XIDeviceEvent * ev) +static void process_xevent_keypress(Ghandles * g, const XKeyEvent * ev) { struct msg_hdr hdr; struct msg_keypress k; - 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 + CHECK_NONMANAGED_WINDOW(g, ev->window); + update_wm_user_time(g, ev->window, ev->time); if (is_special_keypress(g, ev, vm_window->remote_winid)) return; - k.type = ev->evtype; // ev->type is always Generic Event - k.x = ev->event_x; - k.y = ev->event_y; - k.state = ev->mods.effective; - k.keycode = ev->detail; + k.type = ev->type; + k.x = ev->x; + k.y = ev->y; + k.state = ev->state; + k.keycode = ev->keycode; hdr.type = MSG_KEYPRESS; hdr.window = vm_window->remote_winid; write_message(g->vchan, hdr, k); @@ -1432,6 +1392,7 @@ static void process_xevent_button(Ghandles * g, const XButtonEvent * ev) update_wm_user_time(g, ev->window, ev->time); k.type = ev->type; + k.x = ev->x; k.y = ev->y; k.state = ev->state; @@ -1919,20 +1880,6 @@ static void handle_configure_from_vm(Ghandles * g, struct windowdata *vm_window) } } -static void send_keymap_notify(Ghandles * g) -{ - struct msg_hdr hdr; - char keys[32]; - int err = XQueryKeymap(g->display, keys); - if (err) { - fprintf(stderr, "XQueryKeymap failed: %d.\n", err); - return; // non fatal - } - hdr.type = MSG_KEYMAP_NOTIFY; - hdr.window = 0; - write_message(g->vchan, hdr, keys); -} - /* handle local Xserver event: EnterNotify, LeaveNotify * send it to VM, but alwo we use it to fix docked * window position */ @@ -1943,7 +1890,11 @@ static void process_xevent_crossing(Ghandles * g, const XCrossingEvent * ev) CHECK_NONMANAGED_WINDOW(g, ev->window); if (ev->type == EnterNotify) { - send_keymap_notify(g); + char keys[32]; + XQueryKeymap(g->display, keys); + hdr.type = MSG_KEYMAP_NOTIFY; + hdr.window = 0; + write_message(g->vchan, hdr, keys); } /* move tray to correct position in VM */ if (vm_window->is_docked && @@ -1984,20 +1935,35 @@ static void process_xevent_motion(Ghandles * g, const XMotionEvent * ev) /* handle local Xserver event: FocusIn, FocusOut * send to relevant window in VM */ -static void process_xievent_focus(Ghandles * g, const XILeaveEvent * ev) +static void process_xevent_focus(Ghandles * g, const XFocusChangeEvent * ev) { struct msg_hdr hdr; struct msg_focus k; - CHECK_NONMANAGED_WINDOW(g, ev->event); - update_wm_user_time(g, ev->event, ev->time); + CHECK_NONMANAGED_WINDOW(g, ev->window); - if (ev->type == XI_FocusIn) { - send_keymap_notify(g); + /* Ignore everything other than normal, non-temporary focus change. In + * practice it ignores NotifyGrab and NotifyUngrab. VM does not have any + * way to grab focus in dom0, so it shouldn't care about those events. Grab + * is used by window managers during task switching (either classic task + * switcher, or KDE "present windows" feature). + */ + if (ev->mode != NotifyNormal && ev->mode != NotifyWhileGrabbed) + return; + + if (ev->type == FocusIn) { + char keys[32]; + XQueryKeymap(g->display, keys); + hdr.type = MSG_KEYMAP_NOTIFY; + hdr.window = 0; + write_message(g->vchan, hdr, keys); } hdr.type = MSG_FOCUS; hdr.window = vm_window->remote_winid; - k.type = ev->evtype; - k.mode = ev->mode; + k.type = ev->type; + /* override NotifyWhileGrabbed with NotifyNormal b/c VM shouldn't care + * about window manager details during focus switching + */ + k.mode = NotifyNormal; k.detail = ev->detail; write_message(g->vchan, hdr, k); } @@ -2347,7 +2313,11 @@ static void process_xevent_xembed(Ghandles * g, const XClientMessageEvent * ev) } else if (ev->data.l[1] == XEMBED_FOCUS_IN) { struct msg_hdr hdr; struct msg_focus k; - send_keymap_notify(g); + char keys[32]; + XQueryKeymap(g->display, keys); + hdr.type = MSG_KEYMAP_NOTIFY; + hdr.window = 0; + write_message(g->vchan, hdr, keys); hdr.type = MSG_FOCUS; hdr.window = vm_window->remote_winid; k.type = FocusIn; @@ -2362,72 +2332,62 @@ static void process_xevent_xembed(Ghandles * g, const XClientMessageEvent * ev) static void process_xevent(Ghandles * g) { XEvent event_buffer; - XGenericEventCookie *cookie = &event_buffer.xcookie; XNextEvent(g->display, &event_buffer); - if (XGetEventData(g->display, cookie) && - cookie->type == GenericEvent && - cookie->extension == g->xi_opcode) { - 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_xievent_keypress(g, (XIDeviceEvent *)xi_event); - break; - case XI_FocusIn: - case XI_FocusOut: - process_xievent_focus(g, (XILeaveEvent *)xi_event); - break; - } - XFreeEventData(g->display, cookie); - } else { - switch (event_buffer.type) { - case ReparentNotify: - process_xevent_reparent(g, (XReparentEvent *) &event_buffer); - break; - case ConfigureNotify: - process_xevent_configure(g, (XConfigureEvent *) & - event_buffer); - break; - case ButtonPress: - case ButtonRelease: - process_xevent_button(g, (XButtonEvent *) & event_buffer); - break; - case MotionNotify: - process_xevent_motion(g, (XMotionEvent *) & event_buffer); - break; - case EnterNotify: - case LeaveNotify: - process_xevent_crossing(g, - (XCrossingEvent *) & event_buffer); - break; - case Expose: - process_xevent_expose(g, (XExposeEvent *) & event_buffer); - break; - case MapNotify: - process_xevent_mapnotify(g, (XMapEvent *) & event_buffer); - break; - case PropertyNotify: - process_xevent_propertynotify(g, (XPropertyEvent *) & event_buffer); - break; - case ClientMessage: - // fprintf(stderr, "xclient, atom=%s\n", - // XGetAtomName(g->display, - // event_buffer.xclient.message_type)); - if (event_buffer.xclient.message_type == g->xembed_message) { - process_xevent_xembed(g, (XClientMessageEvent *) & - event_buffer); - } else if ((Atom)event_buffer.xclient.data.l[0] == - g->wmDeleteMessage) { - if (g->log_level > 0) - fprintf(stderr, "close for 0x%x\n", - (int) event_buffer.xclient.window); - process_xevent_close(g, - event_buffer.xclient.window); - } - break; + switch (event_buffer.type) { + case KeyPress: + case KeyRelease: + process_xevent_keypress(g, (XKeyEvent *) & event_buffer); + break; + case ReparentNotify: + process_xevent_reparent(g, (XReparentEvent *) &event_buffer); + break; + case ConfigureNotify: + process_xevent_configure(g, (XConfigureEvent *) & + event_buffer); + break; + case ButtonPress: + case ButtonRelease: + process_xevent_button(g, (XButtonEvent *) & event_buffer); + break; + case MotionNotify: + process_xevent_motion(g, (XMotionEvent *) & event_buffer); + break; + case EnterNotify: + case LeaveNotify: + process_xevent_crossing(g, + (XCrossingEvent *) & event_buffer); + break; + case FocusIn: + case FocusOut: + process_xevent_focus(g, + (XFocusChangeEvent *) & event_buffer); + break; + case Expose: + process_xevent_expose(g, (XExposeEvent *) & event_buffer); + break; + case MapNotify: + process_xevent_mapnotify(g, (XMapEvent *) & event_buffer); + break; + case PropertyNotify: + process_xevent_propertynotify(g, (XPropertyEvent *) & event_buffer); + break; + case ClientMessage: +// fprintf(stderr, "xclient, atom=%s\n", +// XGetAtomName(g->display, +// event_buffer.xclient.message_type)); + if (event_buffer.xclient.message_type == g->xembed_message) { + process_xevent_xembed(g, (XClientMessageEvent *) & + event_buffer); + } else if ((Atom)event_buffer.xclient.data.l[0] == + g->wmDeleteMessage) { + if (g->log_level > 0) + fprintf(stderr, "close for 0x%x\n", + (int) event_buffer.xclient.window); + process_xevent_close(g, + event_buffer.xclient.window); } + break; + default:; } } From d965353837aff4a3359efc38c80a6ebaaa214789 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 6 Aug 2022 21:10:59 +0800 Subject: [PATCH 03/13] Add qubes-common (headers) as git submodule --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..8df84382 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "qubes-common"] + path = qubes-common + url = https://github.com/QubesOS/qubes-gui-common/ From cc658aa3e60c9b91fdacdef6133749ea1a446487 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 6 Aug 2022 21:11:33 +0800 Subject: [PATCH 04/13] Migrate build script of gui-daemon to Zig --- gui-daemon/.gitignore | 4 ++- gui-daemon/Makefile | 46 ++++++++++++++------------ gui-daemon/build.zig | 66 ++++++++++++++++++++++++++++++++++++++ gui-daemon/xside.c | 48 ++++++++++++++++++--------- pulse/pacat-simple-vchan.h | 1 - 5 files changed, 128 insertions(+), 37 deletions(-) create mode 100644 gui-daemon/build.zig diff --git a/gui-daemon/.gitignore b/gui-daemon/.gitignore index 6e46ce29..f2baf023 100644 --- a/gui-daemon/.gitignore +++ b/gui-daemon/.gitignore @@ -1,3 +1,5 @@ qubes_guid -qubes-guid qubes-guid.1 +/zig-cache/ +/zig-out/ +/include/ \ No newline at end of file diff --git a/gui-daemon/Makefile b/gui-daemon/Makefile index ef00b809..50b0a5dd 100644 --- a/gui-daemon/Makefile +++ b/gui-daemon/Makefile @@ -19,25 +19,31 @@ # # -MAKEFLAGS := -rR -VCHAN_PKG = $(if $(BACKEND_VMM),vchan-$(BACKEND_VMM),vchan) -CC=gcc -pkgs := x11 xext x11-xcb xcb glib-2.0 xi $(VCHAN_PKG) libpng libnotify libconfig -objs := xside.o png.o trayicon.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 \ - $(shell pkg-config --cflags $(pkgs)) \ - -fvisibility=hidden \ - -fno-strict-aliasing \ - -fno-strict-overflow \ - -fno-delete-null-pointer-checks \ - -Wp,-D_FORTIFY_SOURCE=2 +# MAKEFLAGS := -rR +# VCHAN_PKG = $(if $(BACKEND_VMM),vchan-$(BACKEND_VMM),vchan) +# CC=gcc +# pkgs := x11 xext x11-xcb xcb xi $(VCHAN_PKG) libpng libnotify libconfig +# objs := xside.o png.o trayicon.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 \ +# $(shell pkg-config --cflags $(pkgs)) \ +# -fvisibility=hidden \ +# -fno-strict-aliasing \ +# -fno-strict-overflow \ +# -fno-delete-null-pointer-checks \ +# -Wp,-D_FORTIFY_SOURCE=2 -LDLIBS := $(shell pkg-config --libs $(pkgs)) +# LDLIBS := $(shell pkg-config --libs $(pkgs)) all: qubes-guid # qubes-guid.1 -vpath %.c ../common -qubes-guid: $(objs) - $(CC) -g -pie -o qubes-guid $(objs) -Wall -lm $(LDLIBS) $(LDFLAGS) -Wl,-Bsymbolic +# vpath %.c ../common +# qubes-guid: $(objs) +# $(CC) -g -pie -o qubes-guid $(objs) -Wall -lm $(LDLIBS) $(LDFLAGS) -Wl,-Bsymbolic + +qubes-guid: zig-out/bin/qubes-guid + cp zig-out/bin/qubes-guid qubes-guid + +zig-out/bin/qubes-guid: + zig build qubes-guid.1: qubes-guid LC_ALL=C help2man --version-string=`cat ../version` --no-info --name="Qubes GUI daemon" ./qubes-guid > qubes-guid.1 @@ -45,6 +51,6 @@ qubes-guid.1: qubes-guid clean: rm -f qubes-guid ./*.o ./*~ -%.o: %.c Makefile - $(CC) -MD -MP -MF $@.dep -c -o $@ $(extra_cflags) $(CFLAGS) $< --include *.dep +# %.o: %.c Makefile +# $(CC) -MD -MP -MF $@.dep -c -o $@ $(extra_cflags) $(CFLAGS) $< +# -include *.dep diff --git a/gui-daemon/build.zig b/gui-daemon/build.zig new file mode 100644 index 00000000..c4f430a1 --- /dev/null +++ b/gui-daemon/build.zig @@ -0,0 +1,66 @@ +const std = @import("std"); + +const csources = [_][]const u8{ + "xside.c", + "png.c", + "trayicon.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", +}; + +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); + 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); + } + exe.addCSourceFiles(csources[1..], &.{ + + }); + 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); +} diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index 91669932..e132ee78 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -39,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -56,6 +56,7 @@ #include #include #include +#include #include "xside.h" #include "txrx.h" #include "double-buffer.h" @@ -82,7 +83,7 @@ static Ghandles ghandles; /* macro used to verify data from VM */ -#define VERIFY(x) do if (!(x) && ask_whether_verify_failed(g, __STRING(x))) return; while(0) +#define VERIFY(x) do if (!(x) && ask_whether_verify_failed(g, #x)) return; while(0) /* calculate virtual width */ #define XORG_DEFAULT_XINC 8 @@ -3562,23 +3563,40 @@ static void sighup_signal_handler(int UNUSED(x)) ghandles.reload_requested = 1; } + +/* These variables are global because they + * cause the signal stack to overflow */ +unw_cursor_t u_cursor; +unw_context_t u_context; +char u_buffer[128]; + static void print_backtrace(void) { - void *array[100]; - size_t size; - char **strings; - size_t i; - + int ret = -UNW_ENOINFO; + int depth = 0; if (ghandles.log_level > 1) { - size = backtrace(array, 100); - strings = backtrace_symbols(array, size); - fprintf(stderr, "Obtained %zd stack frames.\n", size); - - for (i = 0; i < size; i++) - printf("%s\n", strings[i]); - - free(strings); + unw_getcontext (&u_context); + if (unw_init_local (&u_cursor, &u_context) < 0) + fprintf(stderr, "unw_init_local failed!\n"); + exit(1); + do { + unw_word_t _ip_offset_from_proc_start; + int ret2 = unw_get_proc_name(&u_cursor, u_buffer, sizeof(u_buffer), &_ip_offset_from_proc_start); + switch (-ret2) { + case 0: + break; + // case UNW_EUNSPEC: + // break; + // case UNW_ENOINFO: + // break; + // case UNW_ENOMEM: + break; + default: + fprintf(stderr, "unw_get_proc_name failed. err: %d \n", -ret2); + exit(1); + } + } while ((ret = unw_step (&u_cursor)) > 0 && ++depth < 128); } } diff --git a/pulse/pacat-simple-vchan.h b/pulse/pacat-simple-vchan.h index 66555a49..10bfae1b 100644 --- a/pulse/pacat-simple-vchan.h +++ b/pulse/pacat-simple-vchan.h @@ -2,7 +2,6 @@ #define __PACAT_SIMPLE_VCHAN_H #include -#include #include #include From 6d4cfc11aeaa9268c94c7018c59cbfb06afa4809 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 6 Aug 2022 22:03:04 +0800 Subject: [PATCH 05/13] [ refactor ] common parts (C&Zig) into xutils.c --- gui-daemon/build.zig | 5 + gui-daemon/xinput-plugin.zig | 87 ++++++++++++ gui-daemon/xinput.patch | 267 ----------------------------------- gui-daemon/xside.c | 20 +-- gui-daemon/xutils.c | 20 +++ gui-daemon/xutils.h | 3 + 6 files changed, 120 insertions(+), 282 deletions(-) create mode 100644 gui-daemon/xinput-plugin.zig delete mode 100644 gui-daemon/xinput.patch create mode 100644 gui-daemon/xutils.c create mode 100644 gui-daemon/xutils.h diff --git a/gui-daemon/build.zig b/gui-daemon/build.zig index c4f430a1..c7cca8d5 100644 --- a/gui-daemon/build.zig +++ b/gui-daemon/build.zig @@ -2,6 +2,7 @@ const std = @import("std"); const csources = [_][]const u8{ "xside.c", + "xutils.c", "png.c", "trayicon.c", "../gui-common/double-buffer.c", @@ -46,6 +47,10 @@ pub fn build(b: *std.build.Builder) void { exe.addCSourceFiles(csources[1..], &.{ }); + + const obj_xinput_plug = b.addObject("qubes-daemon-xinput-plugin", "xinput-plugin.zig"); + exe.addObject(obj_xinput_plug); + exe.install(); const run_cmd = exe.run(); diff --git a/gui-daemon/xinput-plugin.zig b/gui-daemon/xinput-plugin.zig new file mode 100644 index 00000000..0daa5fa9 --- /dev/null +++ b/gui-daemon/xinput-plugin.zig @@ -0,0 +1,87 @@ +// TODO: +// + send_keymap_notify +// + write_message -> .h +// copy verbatim: + // - process_xevent_focus + // - process_xevent_key???? + +const c = @cImport({ + @cInclude("stdio.h"); + @cInclude("stdint.h"); + @cInclude("qubes-gui-protocol.h"); + @cInclude("xside.h"); + @cInclude("libvchan.h"); +}); + +const libvchan_t = c.lib_vchan_t; +const Ghandles = c.Ghandles; +const msg_hdr_t = c.msg_hdr; + +extern fn real_write_message(vchan: ?*libvchan_t, hdr: [*c]u8, size: c_int, data: [*c]u8, datasize: c_int) c_int; +extern fn send_keymap_notify(g: *Ghandles) void; + +fn write_message(vchan: *libvchan_t, header: msg_hdr_t, body: []const u8) c_int { + header.untrusted_len = body.len; + return real_write_message(vchan, @ptrCast([*] u8, &header), @sizeOf(msg_hdr_t), body.ptr, body.len); +} + +// fn qubes_daemon_xinput_plug__init(g: *Ghandles) void { + + +// // init: + +// // // 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); +// } + +// fn qubes_daemon_xinput_plug__process_event(g: Ghandles, event: XEvent) void { +// const cookie = &xevent.cookie; + +// // 2333: + +// // static void process_xevent(Ghandles * g) +// // { +// // XEvent event_buffer; +// // XGenericEventCookie *cookie = &event_buffer.xcookie; +// // XNextEvent(g->display, &event_buffer); +// // if (XGetEventData(g->display, cookie) && +// // cookie->type == GenericEvent && +// // cookie->extension == g->xi_opcode) { +// // 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_xievent_keypress(g, (XIDeviceEvent *)xi_event); +// // break; +// // case XI_FocusIn: +// // case XI_FocusOut: +// // process_xievent_focus(g, (XILeaveEvent *)xi_event); +// // break; +// // } +// // XFreeEventData(g->display, cookie); +// // } else { +// // switch (event_buffer.type) { +// // case ReparentNotify: +// // process_xevent_reparent(g, (XReparentEvent *) &event_buffer); +// // break; +// } diff --git a/gui-daemon/xinput.patch b/gui-daemon/xinput.patch deleted file mode 100644 index 87b0a17c..00000000 --- a/gui-daemon/xinput.patch +++ /dev/null @@ -1,267 +0,0 @@ - gui-daemon/xside.c | 250 +++++++++++++++++++++++++++++++---------------------- - 1 file changed, 145 insertions(+), 105 deletions(-) - -gui-daemon/xside.c --- 1/14 --- C -47 47 #include -48 48 #include -49 49 #include -50 50 #include -.. 51 #include -51 52 #include -52 53 #include -53 54 #include -54 55 #include - -gui-daemon/xside.c --- 2/14 --- C -273 275 return 0; -274 276 } -... 277 -... 278 char error_msg[1024]; -... 279 XGetErrorText(ev->display, ev->error_code, error_msg, sizeof(error_msg)); -... 280 int now = (int) time(NULL); // truncate -... 281 fprintf(stderr, "[%d] Encountered X Error:\n", now); -... 282 fprintf(stderr, error_msg); -... 283 -... 284 -275 285 #ifdef MAKE_X11_ERRORS_FATAL - -gui-daemon/xside.c --- 3/14 --- C -374 (const unsigned char *)&g->time_win, 384 (const unsigned char *)&g->time_win, -375 1); 385 1); -376 (void) XSelectInput(g->display, child_win, 386 (void) XSelectInput(g->display, child_win, -377 ExposureMask | KeyPressMask | KeyReleaseMask | 387 ExposureMask | -378 ButtonPressMask | ButtonReleaseMask | 388 ButtonPressMask | ButtonReleaseMask | -379 PointerMotionMask | EnterWindowMask | LeaveWindowMask | 389 PointerMotionMask | EnterWindowMask | LeaveWindowMask | -380 FocusChangeMask | StructureNotifyMask | PropertyChangeMask); 390 StructureNotifyMask | PropertyChangeMask); -...  391 -...  392 // select xinput events -...  393 XIEventMask xi_mask; -...  394 xi_mask.deviceid = XIAllMasterDevices; // https://stackoverflow.com/questions/440 -...  ... 95001/getting-double-rawkeypress-events-using-xinput2 -...  395 xi_mask.mask_len = XIMaskLen(XI_LASTEVENT); -...  396 if (!(xi_mask.mask = calloc(xi_mask.mask_len, sizeof(char)))) { -...  397 fputs("Out of memory!\n", stderr); -...  398 exit(1); -...  399 } -...  400 XISetMask(xi_mask.mask, XI_KeyPress); -...  401 XISetMask(xi_mask.mask, XI_KeyRelease); -...  402 XISetMask(xi_mask.mask, XI_FocusIn); -...  403 XISetMask(xi_mask.mask, XI_FocusOut); -...  404 -...  405 int err = XISelectEvents(g->display, child_win, &xi_mask, 1); -...  406 if (err) { -...  407 fprintf(stderr, "Failed to subscribe to XI events. ErrCode: %d\n", err); -...  408 exit(1); -...  409 } -...  410 free(xi_mask.mask); -...  411 XSync(g->display, False); -...  412 -...  413 -381 XSetWMProtocols(g->display, child_win, &g->wmDeleteMessage, 1); 414 XSetWMProtocols(g->display, child_win, &g->wmDeleteMessage, 1); - -gui-daemon/xside.c --- 4/14 --- C -656 689 if (!XQueryExtension(g->display, "MIT-SHM", -657 690 &g->shm_major_opcode, &ev_base, &err_base)) -658 691 fprintf(stderr, "MIT-SHM X extension missing!\n"); -... 692 if (!XQueryExtension(g->display, "XInputExtension", &g->xi_opcode, &ev_base, &err_base)) { -... 693 fprintf(stderr, "X Input extension not available. Key press events not available. Upgrade your X11 server now.\n"); -... 694 exit(1); -... 695 } -659 696 /* get the work area */ -660 697 XSelectInput(g->display, g->root_win, PropertyChangeMask); -661 698 update_work_area(g); - -gui-daemon/xside.c --- 5/14 --- C -1250 /* check and handle guid-special keys 1287 /* check and handle guid-special keys -1251 * currently only for inter-vm clipboard copy 1288 * currently only for inter-vm clipboard copy -1252 */ 1289 */ -1253 static int is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_winid) 1290 static int is_special_keypress(Ghandles * g, const XIDeviceEvent * ev, XID remote_wi -....  .... nid) -1254 { 1291 { -1255 struct msg_hdr hdr; 1292 struct msg_hdr hdr; -1256 char *data; 1293 char *data; -1257 int len; 1294 int len; -1258 Time clipboard_file_xevent_time; 1295 Time clipboard_file_xevent_time; -1259 1296 -1260 /* copy */ 1297 /* copy */ -1261 if (((int)ev->state & SPECIAL_KEYS_MASK) == g->copy_seq_mask 1298 if (((int)ev->mods.effective & SPECIAL_KEYS_MASK) == g->copy_seq_mask -1262 && ev->keycode == XKeysymToKeycode(g->display, g->copy_seq_key)) { 1299 && ev->detail == XKeysymToKeycode(g->display, g->copy_seq_key)) { -1263 if (ev->type != KeyPress) 1300 if (ev->evtype != KeyPress) -1264 return 1; 1301 return 1; -1265 g->clipboard_xevent_time = ev->time; 1302 g->clipboard_xevent_time = ev->time; -1266 if (g->qrexec_clipboard) { 1303 if (g->qrexec_clipboard) { - -gui-daemon/xside.c --- 6/14 --- C -1280 } 1317 } -1281 1318 -1282 /* paste */ 1319 /* paste */ -1283 if (((int)ev->state & SPECIAL_KEYS_MASK) == g->paste_seq_mask 1320 if (((int)ev->mods.effective & SPECIAL_KEYS_MASK) == g->paste_seq_mask -1284 && ev->keycode == XKeysymToKeycode(g->display, g->paste_seq_key)) { 1321 && ev->detail == XKeysymToKeycode(g->display, g->paste_seq_key)) { -1285 if (ev->type != KeyPress) 1322 if (ev->evtype != KeyPress) -1286 return 1; 1323 return 1; -1287 inter_appviewer_lock(g, 1); 1324 inter_appviewer_lock(g, 1); -1288 clipboard_file_xevent_time = get_clipboard_file_xevent_timestamp(); 1325 clipboard_file_xevent_time = get_clipboard_file_xevent_timestamp(); - -gui-daemon/xside.c --- 7/14 --- C -1339 1); 1376 1); -1340 } 1377 } -1341 1378 -1342 /* handle local Xserver event: XKeyEvent 1379 /* handle local XInput event -1343 * send it to relevant window in VM 1380 * send it to relevant window in VM -1344 */ 1381 */ -1345 static void process_xevent_keypress(Ghandles * g, const XKeyEvent * ev) 1382 static void process_xievent_keypress(Ghandles * g, const XIDeviceEvent * ev) -1346 { 1383 { -1347 struct msg_hdr hdr; 1384 struct msg_hdr hdr; -1348 struct msg_keypress k; 1385 struct msg_keypress k; -1349 CHECK_NONMANAGED_WINDOW(g, ev->window); 1386 CHECK_NONMANAGED_WINDOW(g, ev->event); -....  1387 // yes, ev->event is the window number -1350 update_wm_user_time(g, ev->window, ev->time); 1388 update_wm_user_time(g, ev->event, ev->time); -....  1389 if (ev->flags & XIKeyRepeat) -....  1390 return; // don't send key repeat events -1351 if (is_special_keypress(g, ev, vm_window->remote_winid)) 1391 if (is_special_keypress(g, ev, vm_window->remote_winid)) -1352 return; 1392 return; -1353 k.type = ev->type; 1393 k.type = ev->evtype; // ev->type is always Generic Event -1354 k.x = ev->x; 1394 k.x = ev->event_x; -1355 k.y = ev->y; 1395 k.y = ev->event_y; -1356 k.state = ev->state; 1396 k.state = ev->mods.effective; -1357 k.keycode = ev->keycode; 1397 k.keycode = ev->detail; -1358 hdr.type = MSG_KEYPRESS; 1398 hdr.type = MSG_KEYPRESS; -1359 hdr.window = vm_window->remote_winid; 1399 hdr.window = vm_window->remote_winid; -1360 write_message(g->vchan, hdr, k); 1400 write_message(g->vchan, hdr, k); - -gui-daemon/xside.c --- 8/14 --- C -1880 1919 } -1881 1920 } -.... 1921 -.... 1922 static void send_keymap_notify(Ghandles * g) -.... 1923 { -.... 1924 struct msg_hdr hdr; -.... 1925 char keys[32]; -.... 1926 int err = XQueryKeymap(g->display, keys); -.... 1927 if (err) { -.... 1928 fprintf(stderr, "XQueryKeymap failed: %d.\n", err); -.... 1929 return; // non fatal -.... 1930 } -.... 1931 hdr.type = MSG_KEYMAP_NOTIFY; -.... 1932 hdr.window = 0; -.... 1933 write_message(g->vchan, hdr, keys); -.... 1934 } -1882 1935 -1883 1936 /* handle local Xserver event: EnterNotify, LeaveNotify -1884 1937 * send it to VM, but alwo we use it to fix docked - -gui-daemon/xside.c --- 9/14 --- C -1890 CHECK_NONMANAGED_WINDOW(g, ev->window); 1943 CHECK_NONMANAGED_WINDOW(g, ev->window); -1891 1944 -1892 if (ev->type == EnterNotify) { 1945 if (ev->type == EnterNotify) { -1893 char keys[32]; ....  -1894 XQueryKeymap(g->display, keys); 1946 send_keymap_notify(g); -1895 hdr.type = MSG_KEYMAP_NOTIFY; ....  -1896 hdr.window = 0; ....  -1897 write_message(g->vchan, hdr, keys); ....  -1898 } 1947 } -1899 /* move tray to correct position in VM */ 1948 /* move tray to correct position in VM */ -1900 if (vm_window->is_docked && 1949 if (vm_window->is_docked && - -gui-daemon/xside.c --- 10/14 --- C -1935 1984 -1936 /* handle local Xserver event: FocusIn, FocusOut 1985 /* handle local Xserver event: FocusIn, FocusOut -1937 * send to relevant window in VM */ 1986 * send to relevant window in VM */ -1938 static void process_xevent_focus(Ghandles * g, const XFocusChangeEvent * ev) 1987 static void process_xievent_focus(Ghandles * g, const XILeaveEvent * ev) -1939 { 1988 { -1940 struct msg_hdr hdr; 1989 struct msg_hdr hdr; -1941 struct msg_focus k; 1990 struct msg_focus k; -1942 CHECK_NONMANAGED_WINDOW(g, ev->window); 1991 CHECK_NONMANAGED_WINDOW(g, ev->event); -1943 ....  -1944 /* Ignore everything other than normal, non-temporary focus change. In ....  -1945 * practice it ignores NotifyGrab and NotifyUngrab. VM does not have any ....  -1946 * way to grab focus in dom0, so it shouldn't care about those events. Grab ....  -1947 * is used by window managers during task switching (either classic task ....  -1948 * switcher, or KDE "present windows" feature). ....  -1949 */ ....  -1950 if (ev->mode != NotifyNormal && ev->mode != NotifyWhileGrabbed) 1992 update_wm_user_time(g, ev->event, ev->time); -1951 return; ....  -1952 1993 -1953 if (ev->type == FocusIn) { 1994 if (ev->type == XI_FocusIn) { -1954 char keys[32]; ....  -1955 XQueryKeymap(g->display, keys); 1995 send_keymap_notify(g); -1956 hdr.type = MSG_KEYMAP_NOTIFY; ....  -1957 hdr.window = 0; ....  -1958 write_message(g->vchan, hdr, keys); ....  -1959 } 1996 } -1960 hdr.type = MSG_FOCUS; 1997 hdr.type = MSG_FOCUS; -1961 hdr.window = vm_window->remote_winid; 1998 hdr.window = vm_window->remote_winid; -1962 k.type = ev->type; 1999 k.type = ev->evtype; -1963 /* override NotifyWhileGrabbed with NotifyNormal b/c VM shouldn't care ....  -1964 * about window manager details during focus switching ....  -1965 */ ....  -1966 k.mode = NotifyNormal; 2000 k.mode = ev->mode; -1967 k.detail = ev->detail; 2001 k.detail = ev->detail; -1968 write_message(g->vchan, hdr, k); 2002 write_message(g->vchan, hdr, k); -1969 } 2003 } - -gui-daemon/xside.c --- 11/14 --- C -2313 } else if (ev->data.l[1] == XEMBED_FOCUS_IN) { 2347 } else if (ev->data.l[1] == XEMBED_FOCUS_IN) { -2314 struct msg_hdr hdr; 2348 struct msg_hdr hdr; -2315 struct msg_focus k; 2349 struct msg_focus k; -2316 char keys[32]; ....  -2317 XQueryKeymap(g->display, keys); 2350 send_keymap_notify(g); -2318 hdr.type = MSG_KEYMAP_NOTIFY; ....  -2319 hdr.window = 0; ....  -2320 write_message(g->vchan, hdr, keys); ....  -2321 hdr.type = MSG_FOCUS; 2351 hdr.type = MSG_FOCUS; -2322 hdr.window = vm_window->remote_winid; 2352 hdr.window = vm_window->remote_winid; -2323 k.type = FocusIn; 2353 k.type = FocusIn; - -gui-daemon/xside.c --- 12/14 --- C -2332 static void process_xevent(Ghandles * g) 2362 static void process_xevent(Ghandles * g) -2333 { 2363 { -2334 XEvent event_buffer; 2364 XEvent event_buffer; -....  2365 XGenericEventCookie *cookie = &event_buffer.xcookie; -2335 XNextEvent(g->display, &event_buffer); 2366 XNextEvent(g->display, &event_buffer); -2336 switch (event_buffer.type) { 2367 if (XGetEventData(g->display, cookie) && -2337 case KeyPress: 2368 cookie->type == GenericEvent && -2338 case KeyRelease: 2369 cookie->extension == g->xi_opcode) { -2339 process_xevent_keypress(g, (XKeyEvent *) & event_buffer); 2370 XIEvent* xi_event = cookie->data; // from test_xi2.c in xinput cli utility -2340 break; 2371 -....  2372 switch (xi_event->evtype) { -....  2373 // ideally raw input events are better, but I'm relying on X server's built- -....  .... in event filtering and routing feature here -....  2374 case XI_KeyPress: -....  2375 case XI_KeyRelease: -....  2376 process_xievent_keypress(g, (XIDeviceEvent *)xi_event); -....  2377 break; -....  2378 case XI_FocusIn: -....  2379 case XI_FocusOut: -....  2380 process_xievent_focus(g, (XILeaveEvent *)xi_event); -....  2381 break; -....  2382 } -....  2383 XFreeEventData(g->display, cookie); -....  2384 } else { -....  2385 switch (event_buffer.type) { -2341 case ReparentNotify: 2386 case ReparentNotify: -2342 process_xevent_reparent(g, (XReparentEvent *) &event_buffer); 2387 process_xevent_reparent(g, (XReparentEvent *) &event_buffer); -2343 break; 2388 break; - -gui-daemon/xside.c --- 13/14 --- C -2357 2402 process_xevent_crossing(g, -2358 2403 (XCrossingEvent *) & event_buffer); -2359 2404 break; -2360 ....  case FocusIn: -2361 ....  case FocusOut: -2362 ....  process_xevent_focus(g, -2363 ....  (XFocusChangeEvent *) & event_buffer); -2364 ....  break; -2365 2405 case Expose: -2366 2406 process_xevent_expose(g, (XExposeEvent *) & event_buffer); -2367 2407 break; - -gui-daemon/xside.c --- 14/14 --- C -2387 event_buffer.xclient.window); 2427 event_buffer.xclient.window); -2388 } 2428 } -2389 break; 2429 break; -2390 default:; 2430 } -2391 } 2431 } -2392 } 2432 } -2393 2433 - diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index e132ee78..43424170 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -66,6 +66,7 @@ #include "trayicon.h" #include "shm-args.h" #include "util.h" +#include "xutils.h" /* Supported protocol version */ @@ -1891,11 +1892,7 @@ static void process_xevent_crossing(Ghandles * g, const XCrossingEvent * ev) CHECK_NONMANAGED_WINDOW(g, ev->window); if (ev->type == EnterNotify) { - char keys[32]; - XQueryKeymap(g->display, keys); - hdr.type = MSG_KEYMAP_NOTIFY; - hdr.window = 0; - write_message(g->vchan, hdr, keys); + send_keymap_notify(g); } /* move tray to correct position in VM */ if (vm_window->is_docked && @@ -1952,11 +1949,7 @@ static void process_xevent_focus(Ghandles * g, const XFocusChangeEvent * ev) return; if (ev->type == FocusIn) { - char keys[32]; - XQueryKeymap(g->display, keys); - hdr.type = MSG_KEYMAP_NOTIFY; - hdr.window = 0; - write_message(g->vchan, hdr, keys); + send_keymap_notify(g); } hdr.type = MSG_FOCUS; hdr.window = vm_window->remote_winid; @@ -2312,13 +2305,10 @@ static void process_xevent_xembed(Ghandles * g, const XClientMessageEvent * ev) } } } else if (ev->data.l[1] == XEMBED_FOCUS_IN) { + send_keymap_notify(g); + struct msg_hdr hdr; struct msg_focus k; - char keys[32]; - XQueryKeymap(g->display, keys); - hdr.type = MSG_KEYMAP_NOTIFY; - hdr.window = 0; - write_message(g->vchan, hdr, keys); hdr.type = MSG_FOCUS; hdr.window = vm_window->remote_winid; k.type = FocusIn; diff --git a/gui-daemon/xutils.c b/gui-daemon/xutils.c new file mode 100644 index 00000000..cb1ea224 --- /dev/null +++ b/gui-daemon/xutils.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include "xside.h" +#include "txrx.h" // write_message +#include "qubes-gui-protocol.h" + +void send_keymap_notify(Ghandles * g) +{ + struct msg_hdr hdr; + char keys[32]; + int err = XQueryKeymap(g->display, keys); + if (err) { + fprintf(stderr, "XQueryKeymap failed: %d.\n", err); + return; // non fatal + } + hdr.type = MSG_KEYMAP_NOTIFY; + hdr.window = 0; + write_message(g->vchan, hdr, keys); +} diff --git a/gui-daemon/xutils.h b/gui-daemon/xutils.h new file mode 100644 index 00000000..90da6550 --- /dev/null +++ b/gui-daemon/xutils.h @@ -0,0 +1,3 @@ +#include "xside.h" + +void send_keymap_notify(Ghandles * g); From ab74f62ef9a7e10335d8578f8b6908e56fa72233 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 6 Aug 2022 22:46:37 +0800 Subject: [PATCH 06/13] Port xinput-plugin back to C --- .gitmodules | 3 +- gui-daemon/build.zig | 25 ++++--- gui-daemon/migration.md | 5 ++ gui-daemon/xinput-plugin.c | 131 +++++++++++++++++++++++++++++++++++ gui-daemon/xinput-plugin.h | 3 + gui-daemon/xinput-plugin.zig | 87 ----------------------- gui-daemon/xside.c | 6 +- gui-daemon/xside.h | 11 +++ 8 files changed, 171 insertions(+), 100 deletions(-) create mode 100644 gui-daemon/migration.md create mode 100644 gui-daemon/xinput-plugin.c create mode 100644 gui-daemon/xinput-plugin.h delete mode 100644 gui-daemon/xinput-plugin.zig diff --git a/.gitmodules b/.gitmodules index 8df84382..287e2c1a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "qubes-common"] path = qubes-common - url = https://github.com/QubesOS/qubes-gui-common/ + url = git@github.com:locriacyber/qubes-gui-common.git + branch = xinput2 diff --git a/gui-daemon/build.zig b/gui-daemon/build.zig index c7cca8d5..9cbcb292 100644 --- a/gui-daemon/build.zig +++ b/gui-daemon/build.zig @@ -5,6 +5,7 @@ const csources = [_][]const u8{ "xutils.c", "png.c", "trayicon.c", + "xinput-plugin.c", "../gui-common/double-buffer.c", "../gui-common/txrx-vchan.c", "../gui-common/error.c", @@ -27,13 +28,7 @@ const packages = .{ "libunwind", }; -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); +fn setup_dependencies(exe: *std.build.LibExeObjStep) void { exe.addIncludePath("../qubes-common/include"); exe.addIncludePath("../include"); // exe.addIncludePath("include"); @@ -44,12 +39,24 @@ pub fn build(b: *std.build.Builder) void { 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"); - exe.addObject(obj_xinput_plug); + // 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(); diff --git a/gui-daemon/migration.md b/gui-daemon/migration.md new file mode 100644 index 00000000..09174586 --- /dev/null +++ b/gui-daemon/migration.md @@ -0,0 +1,5 @@ +## C<->Zig interop tips + +translate command: + +zig translate-c -I/usr/include -I../include/ -I/usr/include/vchan-xen -I/usr/include/libpng16 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-4 -I/usr/include/libmount -I/usr/include/blkid xside.c diff --git a/gui-daemon/xinput-plugin.c b/gui-daemon/xinput-plugin.c new file mode 100644 index 00000000..7dab1a67 --- /dev/null +++ b/gui-daemon/xinput-plugin.c @@ -0,0 +1,131 @@ +// TODO: version detection + +#include +#include "xside.h" +#include "xutils.h" +#include "txrx.h" +#include +#include "qubes-gui-protocol.h" + + +void qubes_daemon_xinput_plug__init(Ghandles * g) { + 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); + } +} + +void qubes_daemon_xinput_plug__on_new_window(Ghandles * g, Window child_win) { + // 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); +} + + +/* 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) +{ + 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_KEYPRESS; + hdr.window = vm_window->remote_winid; + + struct msg_xi_keypress k; + // TODO: + // k.type = ev->evtype; // ev->type is always Generic Event + // k.x = ev->event_x; + // k.y = ev->event_y; + // k.state = ev->mods.effective; + // k.keycode = ev->detail; + + 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_FOCUS; + hdr.window = vm_window->remote_winid; + + struct msg_focus k; + // TODO: + // k.type = ev->evtype; + // k.mode = ev->mode; + // k.detail = ev->detail; + write_message(g->vchan, hdr, k); +} + +bool qubes_daemon_xinput_plug__process_xevent__return_is_xinput_event(Ghandles * g, XEvent * xevent) { + 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; +} diff --git a/gui-daemon/xinput-plugin.h b/gui-daemon/xinput-plugin.h new file mode 100644 index 00000000..774bef22 --- /dev/null +++ b/gui-daemon/xinput-plugin.h @@ -0,0 +1,3 @@ +#include "xside.h" +void qubes_daemon_xinput_plug__init(Ghandles * g); +void qubes_daemon_xinput_plug__process_xevent__return_is_xinput_event(Ghandles * g, XEvent * xevent); diff --git a/gui-daemon/xinput-plugin.zig b/gui-daemon/xinput-plugin.zig deleted file mode 100644 index 0daa5fa9..00000000 --- a/gui-daemon/xinput-plugin.zig +++ /dev/null @@ -1,87 +0,0 @@ -// TODO: -// + send_keymap_notify -// + write_message -> .h -// copy verbatim: - // - process_xevent_focus - // - process_xevent_key???? - -const c = @cImport({ - @cInclude("stdio.h"); - @cInclude("stdint.h"); - @cInclude("qubes-gui-protocol.h"); - @cInclude("xside.h"); - @cInclude("libvchan.h"); -}); - -const libvchan_t = c.lib_vchan_t; -const Ghandles = c.Ghandles; -const msg_hdr_t = c.msg_hdr; - -extern fn real_write_message(vchan: ?*libvchan_t, hdr: [*c]u8, size: c_int, data: [*c]u8, datasize: c_int) c_int; -extern fn send_keymap_notify(g: *Ghandles) void; - -fn write_message(vchan: *libvchan_t, header: msg_hdr_t, body: []const u8) c_int { - header.untrusted_len = body.len; - return real_write_message(vchan, @ptrCast([*] u8, &header), @sizeOf(msg_hdr_t), body.ptr, body.len); -} - -// fn qubes_daemon_xinput_plug__init(g: *Ghandles) void { - - -// // init: - -// // // 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); -// } - -// fn qubes_daemon_xinput_plug__process_event(g: Ghandles, event: XEvent) void { -// const cookie = &xevent.cookie; - -// // 2333: - -// // static void process_xevent(Ghandles * g) -// // { -// // XEvent event_buffer; -// // XGenericEventCookie *cookie = &event_buffer.xcookie; -// // XNextEvent(g->display, &event_buffer); -// // if (XGetEventData(g->display, cookie) && -// // cookie->type == GenericEvent && -// // cookie->extension == g->xi_opcode) { -// // 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_xievent_keypress(g, (XIDeviceEvent *)xi_event); -// // break; -// // case XI_FocusIn: -// // case XI_FocusOut: -// // process_xievent_focus(g, (XILeaveEvent *)xi_event); -// // break; -// // } -// // XFreeEventData(g->display, cookie); -// // } else { -// // switch (event_buffer.type) { -// // case ReparentNotify: -// // process_xevent_reparent(g, (XReparentEvent *) &event_buffer); -// // break; -// } diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index 43424170..ae5f76b8 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -754,7 +754,7 @@ void reload(Ghandles * g) { } /* find if window (given by id) is managed by this guid */ -static struct windowdata *check_nonmanaged_window(Ghandles * g, XID id) +struct windowdata *check_nonmanaged_window(Ghandles * g, XID id) { struct genlist *item = list_lookup(g->wid2windowdata, id); if (!item) { @@ -1252,7 +1252,7 @@ static void handle_cursor(Ghandles *g, struct windowdata *vm_window) /* check and handle guid-special keys * currently only for inter-vm clipboard copy */ -static int is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_winid) +bool is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_winid) { struct msg_hdr hdr; char *data; @@ -1331,7 +1331,7 @@ static int is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_wi return 0; } -static void update_wm_user_time(Ghandles *const g, const Window window, +void update_wm_user_time(Ghandles *const g, const Window window, const Time time) { static_assert(sizeof time == sizeof(long), "Wrong size of X11 time"); XChangeProperty(g->display, g->time_win != None ? g->time_win : window, diff --git a/gui-daemon/xside.h b/gui-daemon/xside.h index 8f391e79..5cb9006f 100644 --- a/gui-daemon/xside.h +++ b/gui-daemon/xside.h @@ -241,4 +241,15 @@ struct _global_handles { typedef struct _global_handles Ghandles; +void update_wm_user_time(Ghandles *const g, const Window window, const Time time); +bool is_special_keypress(Ghandles * g, const XKeyEvent * ev, XID remote_winid); + +/* short macro for beginning of each xevent handling function + * checks if this window is managed by guid and declares windowdata struct + * pointer */ +#define CHECK_NONMANAGED_WINDOW(g, id) struct windowdata *vm_window; \ + if (!(vm_window=check_nonmanaged_window(g, id))) return + +struct windowdata *check_nonmanaged_window(Ghandles * g, XID id); + #endif /* _XSIDE_H */ From ff53a725d8b7b5e1ae97c77244e866f5ca4d864b Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 6 Aug 2022 22:58:48 +0800 Subject: [PATCH 07/13] Plug in xinput-plugin --- gui-daemon/migration.md | 2 ++ gui-daemon/xinput-plugin.c | 10 ++++++++-- gui-daemon/xinput-plugin.h | 4 +++- gui-daemon/xside.c | 9 +++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/gui-daemon/migration.md b/gui-daemon/migration.md index 09174586..d38d9832 100644 --- a/gui-daemon/migration.md +++ b/gui-daemon/migration.md @@ -2,4 +2,6 @@ translate command: +``` zig translate-c -I/usr/include -I../include/ -I/usr/include/vchan-xen -I/usr/include/libpng16 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-4 -I/usr/include/libmount -I/usr/include/blkid xside.c +``` diff --git a/gui-daemon/xinput-plugin.c b/gui-daemon/xinput-plugin.c index 7dab1a67..7a1f4251 100644 --- a/gui-daemon/xinput-plugin.c +++ b/gui-daemon/xinput-plugin.c @@ -1,12 +1,13 @@ -// TODO: version detection - #include +#include +#include #include "xside.h" #include "xutils.h" #include "txrx.h" #include #include "qubes-gui-protocol.h" +static int xinput_plugin_enabled = false; void qubes_daemon_xinput_plug__init(Ghandles * g) { int ev_base, err_base; /* ignore */ @@ -14,9 +15,12 @@ void qubes_daemon_xinput_plug__init(Ghandles * g) { 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 @@ -107,6 +111,8 @@ static void process_xinput_focus(Ghandles * g, const XILeaveEvent * ev) } 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 && diff --git a/gui-daemon/xinput-plugin.h b/gui-daemon/xinput-plugin.h index 774bef22..aa914dcc 100644 --- a/gui-daemon/xinput-plugin.h +++ b/gui-daemon/xinput-plugin.h @@ -1,3 +1,5 @@ +#include #include "xside.h" void qubes_daemon_xinput_plug__init(Ghandles * g); -void qubes_daemon_xinput_plug__process_xevent__return_is_xinput_event(Ghandles * g, XEvent * xevent); +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); diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index ae5f76b8..2a0fce8b 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -380,6 +380,9 @@ static Window mkwindow(Ghandles * g, struct windowdata *vm_window) ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | FocusChangeMask | StructureNotifyMask | PropertyChangeMask); + + qubes_daemon_xinput_plug__on_new_window(g, child_win); + XSetWMProtocols(g->display, child_win, &g->wmDeleteMessage, 1); if (g->icon_data) { XChangeProperty(g->display, child_win, g->net_wm_icon, XA_CARDINAL, 32, @@ -658,6 +661,10 @@ static void mkghandles(Ghandles * g) if (!XQueryExtension(g->display, "MIT-SHM", &g->shm_major_opcode, &ev_base, &err_base)) fprintf(stderr, "MIT-SHM X extension missing!\n"); + + // TODO: version detection before enable this + qubes_daemon_xinput_plug__init(g); + /* get the work area */ XSelectInput(g->display, g->root_win, PropertyChangeMask); update_work_area(g); @@ -2324,6 +2331,8 @@ static void process_xevent(Ghandles * g) { XEvent event_buffer; XNextEvent(g->display, &event_buffer); + if (qubes_daemon_xinput_plug__process_xevent__return_is_xinput_event(g, &event_buffer)) return; + switch (event_buffer.type) { case KeyPress: case KeyRelease: From 6b8e94d8daf81d773ee0f0852525af1c8ce1a3d3 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 6 Aug 2022 23:11:38 +0800 Subject: [PATCH 08/13] Implement Xinput Plugin --- gui-daemon/xinput-plugin.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/gui-daemon/xinput-plugin.c b/gui-daemon/xinput-plugin.c index 7a1f4251..710b06fc 100644 --- a/gui-daemon/xinput-plugin.c +++ b/gui-daemon/xinput-plugin.c @@ -49,6 +49,7 @@ void qubes_daemon_xinput_plug__on_new_window(Ghandles * g, Window child_win) { */ 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; @@ -77,12 +78,12 @@ static void process_xinput_key(Ghandles * g, const XIDeviceEvent * ev) hdr.window = vm_window->remote_winid; struct msg_xi_keypress k; - // TODO: - // k.type = ev->evtype; // ev->type is always Generic Event - // k.x = ev->event_x; - // k.y = ev->event_y; - // k.state = ev->mods.effective; - // k.keycode = ev->detail; + 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; write_message(g->vchan, hdr, k); } @@ -99,14 +100,17 @@ static void process_xinput_focus(Ghandles * g, const XILeaveEvent * ev) } struct msg_hdr hdr; - hdr.type = MSG_FOCUS; + hdr.type = MSG_XI_FOCUS; hdr.window = vm_window->remote_winid; - struct msg_focus k; - // TODO: - // k.type = ev->evtype; - // k.mode = ev->mode; - // k.detail = ev->detail; + 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); } From cdc1691b3947f0df96f7ca5d1e025517503dc764 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sun, 7 Aug 2022 00:16:14 +0800 Subject: [PATCH 09/13] Use msg_xi_key instead of msg_xi_keypress --- gui-daemon/xinput-plugin.c | 4 ++-- qubes-common | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 160000 qubes-common diff --git a/gui-daemon/xinput-plugin.c b/gui-daemon/xinput-plugin.c index 710b06fc..737c2fde 100644 --- a/gui-daemon/xinput-plugin.c +++ b/gui-daemon/xinput-plugin.c @@ -74,10 +74,10 @@ static void process_xinput_key(Ghandles * g, const XIDeviceEvent * ev) return; struct msg_hdr hdr; - hdr.type = MSG_XI_KEYPRESS; + hdr.type = MSG_XI_KEY; hdr.window = vm_window->remote_winid; - struct msg_xi_keypress k; + 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 diff --git a/qubes-common b/qubes-common new file mode 160000 index 00000000..30bbc212 --- /dev/null +++ b/qubes-common @@ -0,0 +1 @@ +Subproject commit 30bbc21205286bc724475e6220c1036a6b75b9eb From 67cc755ae09ad4088c5eaf9d2426d3febd608820 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu, 11 Aug 2022 18:30:07 +0800 Subject: [PATCH 10/13] Add protocol version detection Disable XInput extension when protocol version < 1.5 --- gui-daemon/xinput-plugin.c | 60 +++++++++++++++++++++----------------- gui-daemon/xside.c | 4 +-- qubes-common | 2 +- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/gui-daemon/xinput-plugin.c b/gui-daemon/xinput-plugin.c index 737c2fde..4067ad6a 100644 --- a/gui-daemon/xinput-plugin.c +++ b/gui-daemon/xinput-plugin.c @@ -10,6 +10,14 @@ static int xinput_plugin_enabled = false; void qubes_daemon_xinput_plug__init(Ghandles * g) { + // qubes protocol version detection + if (g->protocol_version < PROTOCOL_VERSION(1, 5)) { + fprintf(stderr, "X Input support disabled, client too old.\n"); + 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"); @@ -43,6 +51,32 @@ void qubes_daemon_xinput_plug__on_new_window(Ghandles * g, Window child_win) { 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; +} + /* check and handle guid-special keys * currently only for inter-vm clipboard copy @@ -113,29 +147,3 @@ static void process_xinput_focus(Ghandles * g, const XILeaveEvent * ev) k.modifier_effective = ev->mods.effective; write_message(g->vchan, hdr, k); } - -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; -} diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index 2a0fce8b..dc7df845 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -662,9 +662,6 @@ static void mkghandles(Ghandles * g) &g->shm_major_opcode, &ev_base, &err_base)) fprintf(stderr, "MIT-SHM X extension missing!\n"); - // TODO: version detection before enable this - qubes_daemon_xinput_plug__init(g); - /* get the work area */ XSelectInput(g->display, g->root_win, PropertyChangeMask); update_work_area(g); @@ -4548,6 +4545,7 @@ int main(int argc, char **argv) get_protocol_version(&ghandles); send_xconf(&ghandles); + qubes_daemon_xinput_plug__init(&ghandles); for (;;) { int select_fds[2] = { xfd }; diff --git a/qubes-common b/qubes-common index 30bbc212..ee3b1b94 160000 --- a/qubes-common +++ b/qubes-common @@ -1 +1 @@ -Subproject commit 30bbc21205286bc724475e6220c1036a6b75b9eb +Subproject commit ee3b1b9495fe0b680a2984596b1f9636c4c72003 From cf7cca565ec7ecaa33230655adda7746c8312837 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu, 11 Aug 2022 19:42:32 +0800 Subject: [PATCH 11/13] Make it build with gcc --- gui-daemon/Makefile | 46 ++++++------ gui-daemon/migration.md | 7 -- gui-daemon/xinput-plugin.c | 143 +++++++++++++++++++------------------ gui-daemon/xside.c | 35 ++++++++- 4 files changed, 125 insertions(+), 106 deletions(-) delete mode 100644 gui-daemon/migration.md diff --git a/gui-daemon/Makefile b/gui-daemon/Makefile index 50b0a5dd..2b76cb54 100644 --- a/gui-daemon/Makefile +++ b/gui-daemon/Makefile @@ -19,31 +19,25 @@ # # -# MAKEFLAGS := -rR -# VCHAN_PKG = $(if $(BACKEND_VMM),vchan-$(BACKEND_VMM),vchan) -# CC=gcc -# pkgs := x11 xext x11-xcb xcb xi $(VCHAN_PKG) libpng libnotify libconfig -# objs := xside.o png.o trayicon.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 \ -# $(shell pkg-config --cflags $(pkgs)) \ -# -fvisibility=hidden \ -# -fno-strict-aliasing \ -# -fno-strict-overflow \ -# -fno-delete-null-pointer-checks \ -# -Wp,-D_FORTIFY_SOURCE=2 +MAKEFLAGS := -rR +VCHAN_PKG = $(if $(BACKEND_VMM),vchan-$(BACKEND_VMM),vchan) +CC=gcc +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../qubes-common/include -I../include/ -g -O2 -Wall -Wextra -Werror -pie -fPIC \ + $(shell pkg-config --cflags $(pkgs)) \ + -fvisibility=hidden \ + -fno-strict-aliasing \ + -fno-strict-overflow \ + -fno-delete-null-pointer-checks \ + -Wp,-D_FORTIFY_SOURCE=2 -# LDLIBS := $(shell pkg-config --libs $(pkgs)) +LDLIBS := $(shell pkg-config --libs $(pkgs)) all: qubes-guid # qubes-guid.1 -# vpath %.c ../common -# qubes-guid: $(objs) -# $(CC) -g -pie -o qubes-guid $(objs) -Wall -lm $(LDLIBS) $(LDFLAGS) -Wl,-Bsymbolic - -qubes-guid: zig-out/bin/qubes-guid - cp zig-out/bin/qubes-guid qubes-guid - -zig-out/bin/qubes-guid: - zig build +vpath %.c ../common +qubes-guid: $(objs) + $(CC) -g -pie -o qubes-guid $(objs) -Wall -lm $(LDLIBS) $(LDFLAGS) -Wl,-Bsymbolic qubes-guid.1: qubes-guid LC_ALL=C help2man --version-string=`cat ../version` --no-info --name="Qubes GUI daemon" ./qubes-guid > qubes-guid.1 @@ -51,6 +45,6 @@ qubes-guid.1: qubes-guid clean: rm -f qubes-guid ./*.o ./*~ -# %.o: %.c Makefile -# $(CC) -MD -MP -MF $@.dep -c -o $@ $(extra_cflags) $(CFLAGS) $< -# -include *.dep +%.o: %.c Makefile + $(CC) -MD -MP -MF $@.dep -c -o $@ $(extra_cflags) $(CFLAGS) $< +-include *.dep diff --git a/gui-daemon/migration.md b/gui-daemon/migration.md deleted file mode 100644 index d38d9832..00000000 --- a/gui-daemon/migration.md +++ /dev/null @@ -1,7 +0,0 @@ -## C<->Zig interop tips - -translate command: - -``` -zig translate-c -I/usr/include -I../include/ -I/usr/include/vchan-xen -I/usr/include/libpng16 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-4 -I/usr/include/libmount -I/usr/include/blkid xside.c -``` diff --git a/gui-daemon/xinput-plugin.c b/gui-daemon/xinput-plugin.c index 4067ad6a..a63a7cdb 100644 --- a/gui-daemon/xinput-plugin.c +++ b/gui-daemon/xinput-plugin.c @@ -7,76 +7,6 @@ #include #include "qubes-gui-protocol.h" -static int xinput_plugin_enabled = false; - -void qubes_daemon_xinput_plug__init(Ghandles * g) { - // qubes protocol version detection - if (g->protocol_version < PROTOCOL_VERSION(1, 5)) { - fprintf(stderr, "X Input support disabled, client too old.\n"); - 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; -} - /* check and handle guid-special keys * currently only for inter-vm clipboard copy @@ -147,3 +77,76 @@ static void process_xinput_focus(Ghandles * g, const XILeaveEvent * ev) k.modifier_effective = ev->mods.effective; write_message(g->vchan, hdr, k); } + + +static int xinput_plugin_enabled = false; + +#define PROTOCOL_VERSION(major, minor) (major << 16 | minor) + +void qubes_daemon_xinput_plug__init(Ghandles * g) { + // qubes protocol version detection + if (g->protocol_version < PROTOCOL_VERSION(1, 5)) { + fprintf(stderr, "X Input support disabled, client too old.\n"); + 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; +} diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index dc7df845..2dc42975 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -56,7 +56,11 @@ #include #include #include +#ifdef USE_UNWIND #include +#else +#include +#endif #include "xside.h" #include "txrx.h" #include "double-buffer.h" @@ -67,6 +71,7 @@ #include "shm-args.h" #include "util.h" #include "xutils.h" +#include "xinput-plugin.h" /* Supported protocol version */ @@ -3559,6 +3564,7 @@ static void sighup_signal_handler(int UNUSED(x)) ghandles.reload_requested = 1; } +#ifdef USE_UNWIND /* These variables are global because they * cause the signal stack to overflow */ @@ -3573,9 +3579,10 @@ static void print_backtrace(void) if (ghandles.log_level > 1) { unw_getcontext (&u_context); - if (unw_init_local (&u_cursor, &u_context) < 0) + if (unw_init_local (&u_cursor, &u_context) < 0) { fprintf(stderr, "unw_init_local failed!\n"); - exit(1); + return; + } do { unw_word_t _ip_offset_from_proc_start; int ret2 = unw_get_proc_name(&u_cursor, u_buffer, sizeof(u_buffer), &_ip_offset_from_proc_start); @@ -3590,12 +3597,34 @@ static void print_backtrace(void) break; default: fprintf(stderr, "unw_get_proc_name failed. err: %d \n", -ret2); - exit(1); + return; } } while ((ret = unw_step (&u_cursor)) > 0 && ++depth < 128); } +} +#else +// use glibc +static void print_backtrace(void) +{ + void *array[100]; + size_t size; + char **strings; + size_t i; + + + if (ghandles.log_level > 1) { + size = backtrace(array, 100); + strings = backtrace_symbols(array, size); + fprintf(stderr, "Obtained %zd stack frames.\n", size); + + for (i = 0; i < size; i++) + printf("%s\n", strings[i]); + + free(strings); + } } +#endif /* release all windows mapped memory */ static void release_all_mapped_mfns(void) From e5e7e0cd006f9e88b1bde934e0aba616c4ec77d8 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu, 11 Aug 2022 21:26:06 +0800 Subject: [PATCH 12/13] Send XI KeyPress/Release `flags` for key repeat detection on client side --- gui-daemon/xinput-plugin.c | 3 ++- qubes-common | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gui-daemon/xinput-plugin.c b/gui-daemon/xinput-plugin.c index a63a7cdb..000dd2cd 100644 --- a/gui-daemon/xinput-plugin.c +++ b/gui-daemon/xinput-plugin.c @@ -48,7 +48,8 @@ static void process_xinput_key(Ghandles * g, const XIDeviceEvent * ev) 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); } diff --git a/qubes-common b/qubes-common index ee3b1b94..59dd27dc 160000 --- a/qubes-common +++ b/qubes-common @@ -1 +1 @@ -Subproject commit ee3b1b9495fe0b680a2984596b1f9636c4c72003 +Subproject commit 59dd27dcc99308aff0000d008b01485274642f14 From a1c1b1d4b32616585325923fede50f290715c573 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Thu, 11 Aug 2022 22:33:14 +0800 Subject: [PATCH 13/13] Bump version to 1.5 --- gui-daemon/xinput-plugin.c | 9 +++++---- gui-daemon/xside.c | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gui-daemon/xinput-plugin.c b/gui-daemon/xinput-plugin.c index 000dd2cd..be108031 100644 --- a/gui-daemon/xinput-plugin.c +++ b/gui-daemon/xinput-plugin.c @@ -82,12 +82,13 @@ static void process_xinput_focus(Ghandles * g, const XILeaveEvent * ev) static int xinput_plugin_enabled = false; -#define PROTOCOL_VERSION(major, minor) (major << 16 | minor) - void qubes_daemon_xinput_plug__init(Ghandles * g) { // qubes protocol version detection - if (g->protocol_version < PROTOCOL_VERSION(1, 5)) { - fprintf(stderr, "X Input support disabled, client too old.\n"); + 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"); diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index 2dc42975..22625282 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -76,7 +76,7 @@ /* Supported protocol version */ #define PROTOCOL_VERSION_MAJOR 1 -#define PROTOCOL_VERSION_MINOR 4 +#define PROTOCOL_VERSION_MINOR 5 #define PROTOCOL_VERSION(x, y) ((x) << 16 | (y)) #if !(PROTOCOL_VERSION_MAJOR == QUBES_GUID_PROTOCOL_VERSION_MAJOR && \