diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..287e2c1a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "qubes-common"] + path = qubes-common + url = git@github.com:locriacyber/qubes-gui-common.git + branch = xinput2 diff --git a/archlinux/.gitignore b/archlinux/.gitignore new file mode 100644 index 00000000..e2e4594b --- /dev/null +++ b/archlinux/.gitignore @@ -0,0 +1,3 @@ +*.tar.zst +/src/ +/pkg/ \ No newline at end of file diff --git a/gui-agent/.gitignore b/gui-agent/.gitignore index 7d05a284..510b7d6c 100644 --- a/gui-agent/.gitignore +++ b/gui-agent/.gitignore @@ -1 +1,2 @@ qubes_gui +qubes-gui \ No newline at end of file diff --git a/gui-agent/Makefile b/gui-agent/Makefile index 3b26e887..d86d5152 100644 --- a/gui-agent/Makefile +++ b/gui-agent/Makefile @@ -20,7 +20,7 @@ # CC ?= gcc -CFLAGS += -I../include/ `pkg-config --cflags vchan` -g -Wall -Wextra -Werror -pie -fPIC +CFLAGS += -I../qubes-common/include -I../include/ `pkg-config --cflags vchan` -g -Wall -Wextra -Werror -pie -fPIC OBJS = vmside.o \ ../gui-common/txrx-vchan.o ../gui-common/error.o ../common/list.o ../gui-common/encoding.o LIBS = -lX11 -lXdamage -lXcomposite -lXfixes `pkg-config --libs vchan` -lqubesdb diff --git a/gui-agent/vmside.c b/gui-agent/vmside.c index fd76f4db..74d64ec6 100644 --- a/gui-agent/vmside.c +++ b/gui-agent/vmside.c @@ -20,8 +20,13 @@ * */ +#define _POSIX_C_SOURCE 200112L // getopt + +#include +#include #include #include +#include #include #include #include @@ -40,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -62,7 +68,7 @@ /* Supported protocol version */ #define PROTOCOL_VERSION_MAJOR 1 -#define PROTOCOL_VERSION_MINOR 3 +#define PROTOCOL_VERSION_MINOR 5 #define PROTOCOL_VERSION (PROTOCOL_VERSION_MAJOR << 16 | PROTOCOL_VERSION_MINOR) #if !(PROTOCOL_VERSION_MAJOR == QUBES_GUID_PROTOCOL_VERSION_MAJOR && \ @@ -94,6 +100,7 @@ struct _global_handles { Atom wm_class; /* Atom: WM_CLASS */ Atom tray_selection; /* Atom: _NET_SYSTEM_TRAY_SELECTION_S */ Atom tray_opcode; /* Atom: _NET_SYSTEM_TRAY_OPCODE */ + int xi_opcode; Atom xembed_info; /* Atom: _XEMBED_INFO */ Atom utf8_string_atom; /* Atom: UTF8_STRING */ Atom wm_state; /* Atom: WM_STATE */ @@ -869,6 +876,7 @@ static void process_xevent_destroy(Ghandles * g, XID window) } SKIP_NONMANAGED_WINDOW; + XUngrabPointer(g->display, CurrentTime); if (g->log_level > 0) fprintf(stderr, "handle destroy 0x%x\n", (int) window); hdr.type = MSG_DESTROY; @@ -1600,23 +1608,25 @@ static void mkghandles(Ghandles * g) g->clipboard_data_len = 0; } -static void handle_keypress(Ghandles * g, XID UNUSED(winid)) + +// return true if the key is already handled +static bool reset_modifier_keys(Ghandles * g, uint32_t key_evtype, uint32_t key_detail, uint32_t key_modifier) { - struct msg_keypress key; XkbStateRec state; - read_data(g->vchan, (char *) &key, sizeof(key)); // sync modifiers state if (XkbGetState(g->display, XkbUseCoreKbd, &state) != Success) { if (g->log_level > 0) fprintf(stderr, "failed to get modifier state\n"); - state.mods = key.state; + state.mods = key_modifier; } if (!g->sync_all_modifiers) { // ignore all but CapsLock state.mods &= LockMask; - key.state &= LockMask; + key_modifier &= LockMask; } - if (state.mods != key.state) { + bool is_press = key_evtype == KeyPress; + bool already_handled = false; + if (state.mods != key_modifier) { XModifierKeymap *modmap; int mod_index; int mod_mask; @@ -1636,7 +1646,8 @@ static void handle_keypress(Ghandles * g, XID UNUSED(winid)) // #define Mod4MapIndex 6 // #define Mod5MapIndex 7 for (mod_index = 0; mod_index < 8; mod_index++) { - if (modmap->modifiermap[mod_index*modmap->max_keypermod] == 0x00) { + uint32_t keycode = modmap->modifiermap[mod_index*modmap->max_keypermod]; + if (keycode == 0x00) { if (g->log_level > 1) fprintf(stderr, "ignoring disabled modifier %d\n", mod_index); // no key set for this modifier, ignore @@ -1645,31 +1656,52 @@ static void handle_keypress(Ghandles * g, XID UNUSED(winid)) mod_mask = (1<modifiermap[mod_index*modmap->max_keypermod], 1); - feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 0); + if ((state.mods & mod_mask) ^ (key_modifier & mod_mask)) { + feed_xdriver(g, 'K', keycode, 1); + feed_xdriver(g, 'K', keycode, 0); } } else { - if ((state.mods & mod_mask) && !(key.state & mod_mask)) - feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 0); - else if (!(state.mods & mod_mask) && (key.state & mod_mask)) - feed_xdriver(g, 'K', modmap->modifiermap[mod_index*modmap->max_keypermod], 1); + if ((state.mods & mod_mask) && !(key_modifier & mod_mask)) { + // need to release + if (keycode == key_detail && !is_press) already_handled = true; + // todo: feed device id as well + feed_xdriver(g, 'K', keycode, 0); + } + else if (!(state.mods & mod_mask) && (key_modifier & mod_mask)) { + // need to press + if (keycode == key_detail && is_press) already_handled = true; + // todo: feed device id as well + feed_xdriver(g, 'K', keycode, 1); + } } } XFreeModifiermap(modmap); } } + return already_handled; +} - feed_xdriver(g, 'K', key.keycode, key.type == KeyPress ? 1 : 0); +static void handle_xi_key(Ghandles * g, XID UNUSED(winid)) +{ + struct msg_xi_key key; + read_data(g->vchan, (char *) &key, sizeof(key)); + // TODO: make this an tweakable option (in config file) + // drop key repeat + if (key.flags & XIKeyRepeat) return; + bool is_press = key.evtype == KeyPress; + bool already_handled = reset_modifier_keys(g, key.evtype, key.detail, key.modifier_effective); + if (!already_handled) feed_xdriver(g, 'K', key.detail, is_press); } +static void handle_focus_helper(Ghandles * g, XID winid, struct msg_xi_focus msg); + static void handle_button(Ghandles * g, XID winid) { - struct msg_button key; + struct msg_button msg; struct genlist *l = list_lookup(windows_list, winid); - read_data(g->vchan, (char *) &key, sizeof(key)); + read_data(g->vchan, (char *) &msg, sizeof(msg)); if (l && l->data && ((struct window_data*)l->data)->is_docked) { /* get position of embeder, not icon itself*/ winid = ((struct window_data*)l->data)->embeder; @@ -1679,19 +1711,43 @@ static void handle_button(Ghandles * g, XID winid) if (g->log_level > 1) fprintf(stderr, "send buttonevent, win 0x%x type=%d button=%d\n", - (int) winid, key.type, key.button); - feed_xdriver(g, 'B', key.button, key.type == ButtonPress ? 1 : 0); + (int) winid, msg.type, msg.button); + + bool is_button_press = msg.type == ButtonPress; + + // click first, or some weird race condition may happen + // some applications close all context menus when focused, and the click will happen after + feed_xdriver(g, 'B', msg.button, is_button_press ? 1 : 0); + + // Fake a "focus in" when mouse down on unfocused window. + if (is_button_press) { + // int _return_to; + // XID focused_winid; + // XGetInputFocus(g->display, &focused_winid, &_return_to); + // bool a = focused_winid != winid + // TODO: check if window is "top-level" (has border), and only focus if it or its child was not focused + // some applications get confused if you give focus to its context menu + // I'm not sure how this works yet + bool need_focus = false; + if (need_focus) { + struct msg_xi_focus msg_focusin; + msg_focusin.evtype = FocusIn; + msg_focusin.mode = NotifyNormal; + msg_focusin.detail = NotifyNonlinear; + handle_focus_helper(g, winid, msg_focusin); + } + } } static void handle_motion(Ghandles * g, XID winid) { - struct msg_motion key; + struct msg_motion msg; // XMotionEvent event; XWindowAttributes attr; int ret; struct genlist *l = list_lookup(windows_list, winid); - read_data(g->vchan, (char *) &key, sizeof(key)); + read_data(g->vchan, (char *) &msg, sizeof(msg)); if (l && l->data && ((struct window_data*)l->data)->is_docked) { /* get position of embeder, not icon itself*/ winid = ((struct window_data*)l->data)->embeder; @@ -1704,14 +1760,14 @@ static void handle_motion(Ghandles * g, XID winid) return; }; - feed_xdriver(g, 'M', attr.x + key.x, attr.y + key.y); + feed_xdriver(g, 'M', attr.x + msg.x, attr.y + msg.y); } // ensure that LeaveNotify is delivered to the window - if pointer is still // above this window, place stub window between pointer and the window static void handle_crossing(Ghandles * g, XID winid) { - struct msg_crossing key; + struct msg_crossing msg; XWindowAttributes attr; int ret; struct genlist *l = list_lookup(windows_list, winid); @@ -1723,9 +1779,9 @@ static void handle_crossing(Ghandles * g, XID winid) winid = ((struct window_data*)l->data)->embeder; } - read_data(g->vchan, (char *) &key, sizeof(key)); + read_data(g->vchan, (char *) &msg, sizeof(msg)); - if (key.mode != NotifyNormal) + if (msg.mode != NotifyNormal) return; ret = XGetWindowAttributes(g->display, winid, &attr); if (ret != 1) { @@ -1735,11 +1791,11 @@ static void handle_crossing(Ghandles * g, XID winid) return; }; - if (key.type == EnterNotify) { + if (msg.type == EnterNotify) { // hide stub window XUnmapWindow(g->display, g->stub_win); - feed_xdriver(g, 'M', attr.x + key.x, attr.y + key.y); - } else if (key.type == LeaveNotify) { + feed_xdriver(g, 'M', attr.x + msg.x, attr.y + msg.y); + } else if (msg.type == LeaveNotify) { XID window_under_pointer, root_returned; int root_x, root_y, win_x, win_y; unsigned int mask_return; @@ -1763,7 +1819,7 @@ static void handle_crossing(Ghandles * g, XID winid) XRaiseWindow(g->display, g->stub_win); } } else { - fprintf(stderr, "Invalid crossing event: %d\n", key.type); + fprintf(stderr, "Invalid crossing event: %d\n", msg.type); } } @@ -1784,59 +1840,75 @@ static void take_focus(Ghandles * g, XID winid) if (g->log_level > 0) fprintf(stderr, "WM_TAKE_FOCUS sent for 0x%x\n", (int) winid); - } -static void handle_focus(Ghandles * g, XID winid) +static void handle_focus_helper(Ghandles * g, XID winid, struct msg_xi_focus msg) { - struct msg_focus key; struct genlist *l; - int input_hint; - int use_take_focus; - - read_data(g->vchan, (char *) &key, sizeof(key)); - if (key.type == FocusIn - && (key.mode == NotifyNormal || key.mode == NotifyUngrab)) { + bool use_take_focus = false; + bool input_hint = false; + if ( (l=list_lookup(windows_list, winid)) && (l->data) ) + input_hint = ((struct window_data*)l->data)->input_hint; + else { + fprintf(stderr, "WARNING handle_xi_focus: Window 0x%x data not initialized", (int)winid); + input_hint = true; + } + + if (msg.evtype == FocusIn) { + if (msg.mode == NotifyNormal) { + XRaiseWindow(g->display, winid); + + + if ( (l=list_lookup(windows_list, winid)) && (l->data) ) { + use_take_focus = ((struct window_data*)l->data)->support_take_focus; + if (((struct window_data*)l->data)->is_docked) + XRaiseWindow(g->display, ((struct window_data*)l->data)->embeder); + } else { + fprintf(stderr, "WARNING handle_xi_focus: Window 0x%x data not initialized", (int)winid); + } - XRaiseWindow(g->display, winid); + if (input_hint) { + if (g->log_level > 1) + fprintf(stderr, "0x%x gained focus\n", (int) winid); + XSetInputFocus(g->display, winid, RevertToParent, g->time); + } - if ( (l=list_lookup(windows_list, winid)) && (l->data) ) { - input_hint = ((struct window_data*)l->data)->input_hint; - use_take_focus = ((struct window_data*)l->data)->support_take_focus; - if (((struct window_data*)l->data)->is_docked) - XRaiseWindow(g->display, ((struct window_data*)l->data)->embeder); + // Do not send WM_TAKE_FOCUS if the window doesn't support it + if (use_take_focus) + take_focus(g, winid); + } + if (msg.mode == NotifyGrab) { + XGrabPointer(g->display, winid, false, 0, GrabModeSync, GrabModeSync, None, None, CurrentTime); } else { - fprintf(stderr, "WARNING handle_focus: Window 0x%x data not initialized", (int)winid); - input_hint = True; - use_take_focus = False; + XUngrabPointer(g->display, CurrentTime); } - - // Give input focus only to window that set the input hint - if (input_hint) - XSetInputFocus(g->display, winid, RevertToParent, g->time); - - // Do not send take focus if the window doesn't support it - if (use_take_focus) - take_focus(g, winid); - - if (g->log_level > 1) - fprintf(stderr, "0x%x raised\n", (int) winid); - } else if (key.type == FocusOut - && (key.mode == NotifyNormal - || key.mode == NotifyUngrab)) { - if ( (l=list_lookup(windows_list, winid)) && (l->data) ) - input_hint = ((struct window_data*)l->data)->input_hint; - else { - fprintf(stderr, "WARNING handle_focus: Window 0x%x data not initialized", (int)winid); - input_hint = True; + } else if (msg.evtype == FocusOut && input_hint) { + if (msg.mode == NotifyNormal) { + int ignore; + XID winid_focused; + XGetInputFocus(g->display, &winid_focused, &ignore); + if (winid_focused == winid) { + XSetInputFocus(g->display, None, RevertToParent, g->time); + if (g->log_level > 1) + fprintf(stderr, "0x%x lost focus\n", (int) winid); + } + } + if (msg.mode == NotifyGrab) { + XGrabPointer(g->display, g->root_win, false, 0, GrabModeSync, GrabModeSync, None, None, CurrentTime); + } + if (msg.mode == NotifyUngrab) { + XUngrabPointer(g->display, CurrentTime); } - if (input_hint) - XSetInputFocus(g->display, None, RevertToParent, g->time); - - if (g->log_level > 1) - fprintf(stderr, "0x%x lost focus\n", (int) winid); } +} +static void handle_xi_focus(Ghandles * g, XID winid) +{ + struct msg_xi_focus msg; + read_data(g->vchan, (char *) &msg, sizeof(msg)); + // disabled, since it doesn't seem to do much + // reset_modifier_keys(g, 0, 0, msg.modifier_effective); // release keys when focus out + return handle_focus_helper(g, winid, msg); } static int bitset(unsigned char *keys, int num) @@ -2073,8 +2145,8 @@ static void handle_message(Ghandles * g) if (g->log_level > 1) fprintf(stderr, "received message type %d for 0x%x\n", hdr.type, hdr.window); switch (hdr.type) { - case MSG_KEYPRESS: - handle_keypress(g, hdr.window); + case MSG_XI_KEY: + handle_xi_key(g, hdr.window); break; case MSG_CONFIGURE: handle_configure(g, hdr.window); @@ -2094,8 +2166,8 @@ static void handle_message(Ghandles * g) case MSG_CROSSING: handle_crossing(g, hdr.window); break; - case MSG_FOCUS: - handle_focus(g, hdr.window); + case MSG_XI_FOCUS: + handle_xi_focus(g, hdr.window); break; case MSG_CLIPBOARD_REQ: handle_clipboard_req(g, hdr.window); @@ -2140,6 +2212,8 @@ static pid_t get_xconf_and_run_x(Ghandles *g) static void send_protocol_version(libvchan_t *vchan) { uint32_t version = PROTOCOL_VERSION; + // todo: remove + fprintf(stderr, "send version %d", version); write_struct(vchan, version); } @@ -2175,6 +2249,7 @@ static void handle_sigterm() static void usage() { fprintf(stderr, "Usage: qubes_gui [options]\n"); + fprintf(stderr, " -V show version\n"); fprintf(stderr, " -v increase log verbosity\n"); fprintf(stderr, " -q decrease log verbosity\n"); fprintf(stderr, " -m sync all modifiers before key event (default)\n"); @@ -2198,7 +2273,7 @@ static void parse_args(Ghandles * g, int argc, char **argv) g->sync_all_modifiers = 1; g->composite_redirect_automatic = 1; g->domid = 0; - while ((opt = getopt(argc, argv, "qvchmMd:")) != -1) { + while ((opt = getopt(argc, argv, "qvVchmMd:")) != -1) { switch (opt) { case 'q': g->log_level--; @@ -2206,6 +2281,9 @@ static void parse_args(Ghandles * g, int argc, char **argv) case 'v': g->log_level++; break; + case 'V': + fprintf(stderr, "qubes-gui version %d.%d\n", PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR); + exit(0); case 'm': g->sync_all_modifiers = 1; break; @@ -2271,6 +2349,7 @@ int main(int argc, char **argv) XSelectInput(g.display, RootWindow(g.display, i), SubstructureNotifyMask); + if (!XDamageQueryExtension(g.display, &damage_event, &damage_error)) { @@ -2291,6 +2370,12 @@ int main(int argc, char **argv) XFixesDisplayCursorNotifyMask); } else fprintf(stderr, "XFixes not available, cursor shape handling off"); + + int ev_base, err_base; // ignore those + 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."); + return 1; + } XAutoRepeatOff(g.display); signal(SIGCHLD, SIG_IGN); diff --git a/qubes-common b/qubes-common new file mode 160000 index 00000000..59dd27dc --- /dev/null +++ b/qubes-common @@ -0,0 +1 @@ +Subproject commit 59dd27dcc99308aff0000d008b01485274642f14