diff --git a/debian/control b/debian/control index a15f2ce9..d3bb3a3b 100644 --- a/debian/control +++ b/debian/control @@ -13,6 +13,7 @@ Build-Depends: libx11-dev, libxcomposite-dev, libxdamage-dev, + libxfixes-dev, x11proto-xf86dga-dev, libxt-dev, libxen-dev, @@ -38,6 +39,7 @@ Depends: qubesdb-vm (>= 4.1.4), libxdamage1, libxcomposite1, + libxfixes3, libxt6, libx11-6, python3, diff --git a/gui-agent/Makefile b/gui-agent/Makefile index b6ecf96b..d68b114a 100644 --- a/gui-agent/Makefile +++ b/gui-agent/Makefile @@ -23,7 +23,7 @@ CC ?= gcc CFLAGS += -I../include/ `pkg-config --cflags vchan-$(BACKEND_VMM)` -g -Wall -Wextra -Werror -pie -fPIC OBJS = vmside.o \ ../gui-common/txrx-vchan.o ../gui-common/error.o ../common/list.o -LIBS = -lX11 -lXdamage -lXcomposite `pkg-config --libs vchan-$(BACKEND_VMM)` -lqubesdb +LIBS = -lX11 -lXdamage -lXcomposite -lXfixes `pkg-config --libs vchan-$(BACKEND_VMM)` -lqubesdb all: qubes-gui diff --git a/gui-agent/vmside.c b/gui-agent/vmside.c index 8b674c2d..c4733976 100644 --- a/gui-agent/vmside.c +++ b/gui-agent/vmside.c @@ -20,6 +20,7 @@ * */ +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include #include "xdriver-shm-cmd.h" @@ -51,7 +53,7 @@ #define SOCKET_ADDRESS "/var/run/xf86-qubes-socket" -#define QUBES_GUI_PROTOCOL_VERSION_LINUX (1 << 16 | 1) +#define QUBES_GUI_PROTOCOL_VERSION_LINUX (1 << 16 | 3) #ifdef __GNUC__ # define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) @@ -60,6 +62,7 @@ #endif int damage_event, damage_error; +int xfixes_event, xfixes_error; /* from gui-common/error.c */ extern int print_x11_errors; @@ -113,6 +116,130 @@ Ghandles *ghandles_for_vchan_reinitialize; #define SKIP_NONMANAGED_WINDOW if (!list_lookup(windows_list, window)) return +/* Cursor name translation. See X11/cursorfont.h. */ + +struct supported_cursor { + const char *name; + uint32_t cursor_id; +}; + +struct supported_cursor supported_cursors[] = { + /* Names as defined by Xlib. Most programs will use these. */ + { "X_cursor", XC_X_cursor }, + { "arrow", XC_arrow }, + { "based_arrow_down", XC_based_arrow_down }, + { "based_arrow_up", XC_based_arrow_up }, + { "boat", XC_boat }, + { "bogosity", XC_bogosity }, + { "bottom_left_corner", XC_bottom_left_corner }, + { "bottom_right_corner", XC_bottom_right_corner }, + { "bottom_side", XC_bottom_side }, + { "bottom_tee", XC_bottom_tee }, + { "box_spiral", XC_box_spiral }, + { "center_ptr", XC_center_ptr }, + { "circle", XC_circle }, + { "clock", XC_clock }, + { "coffee_mug", XC_coffee_mug }, + { "cross", XC_cross }, + { "cross_reverse", XC_cross_reverse }, + { "crosshair", XC_crosshair }, + { "diamond_cross", XC_diamond_cross }, + { "dot", XC_dot }, + { "dotbox", XC_dotbox }, + { "double_arrow", XC_double_arrow }, + { "draft_large", XC_draft_large }, + { "draft_small", XC_draft_small }, + { "draped_box", XC_draped_box }, + { "e xchange", XC_exchange }, + { "fleur", XC_fleur }, + { "gobbler", XC_gobbler }, + { "gumby", XC_gumby }, + { "hand1", XC_hand1 }, + { "hand2", XC_hand2 }, + { "heart", XC_heart }, + { "icon", XC_icon }, + { "iron_cross", XC_iron_cross }, + { "left_ptr", XC_left_ptr }, + { "left_side", XC_left_side }, + { "left_tee", XC_left_tee }, + { "leftbutton", XC_leftbutton }, + { "ll_angle", XC_ll_angle }, + { "lr_angle", XC_lr_angle }, + { "man", XC_man }, + { "middlebutton", XC_middlebutton }, + { "mouse", XC_mouse }, + { "pencil", XC_pencil }, + { "pirate", XC_pirate }, + { "plus", XC_plus }, + { "question_arrow", XC_question_arrow }, + { "right_ptr", XC_right_ptr }, + { "right_side", XC_right_side }, + { "right_tee", XC_right_tee }, + { "rightbutton", XC_rightbutton }, + { "rtl_logo", XC_rtl_logo }, + { "sailboat", XC_sailboat }, + { "sb_down_arrow", XC_sb_down_arrow }, + { "sb_h_double_arrow", XC_sb_h_double_arrow }, + { "sb_left_arrow", XC_sb_left_arrow }, + { "sb_right_arrow", XC_sb_right_arrow }, + { "sb_up_arrow", XC_sb_up_arrow }, + { "sb_v_double_arrow", XC_sb_v_double_arrow }, + { "shuttle", XC_shuttle }, + { "sizing", XC_sizing }, + { "spider", XC_spider }, + { "spraycan", XC_spraycan }, + { "star", XC_star }, + { "target", XC_target }, + { "tcross", XC_tcross }, + { "top_left_arrow", XC_top_left_arrow }, + { "top_left_corner", XC_top_left_corner }, + { "top_right_corner", XC_top_right_corner }, + { "top_side", XC_top_side }, + { "top_tee", XC_top_tee }, + { "trek", XC_trek }, + { "ul_angle", XC_ul_angle }, + { "umbrella", XC_umbrella }, + { "ur_angle", XC_ur_angle }, + { "watch", XC_watch }, + { "xterm", XC_xterm }, + + /* Chromium (and derived projects) use different names. + * See: https://github.com/chromium/chromium/blob/ccd149af47315e4c6f2fc45d55be1b271f39062c/ui/base/cursor/cursor_loader_x11.cc#L25 + */ + { "pointer", XC_hand2 }, + { "progress", XC_watch }, + { "wait", XC_watch }, + { "cell", XC_plus }, + { "all-scroll", XC_fleur }, + { "v-scroll", XC_fleur }, + { "h-scroll", XC_fleur }, + { "crosshair", XC_cross }, + { "text", XC_xterm }, + // { "not-allowed", x11::None }, + { "grabbing", XC_hand2 }, + { "col-resize", XC_sb_h_double_arrow }, + { "row-resize", XC_sb_v_double_arrow }, + { "n-resize", XC_top_side }, + { "e-resize", XC_right_side }, + { "s-resize", XC_bottom_side }, + { "w-resize", XC_left_side }, + { "ne-resize", XC_top_right_corner }, + { "nw-resize", XC_top_left_corner }, + { "se-resize", XC_bottom_right_corner }, + { "sw-resize", XC_bottom_left_corner }, + { "ew-resize", XC_sb_h_double_arrow }, + { "ns-resize", XC_sb_v_double_arrow }, + // { "nesw-resize",x11::None}, + // { "nwse-resize",x11::None}, + { "dnd-none", XC_hand2 }, + { "dnd-move", XC_hand2 }, + { "dnd-copy", XC_hand2 }, + { "dnd-link", XC_hand2 }, + + // end marker + { NULL, 0 } +}; + void send_wmname(Ghandles * g, XID window); void send_wmnormalhints(Ghandles * g, XID window, int ignore_fail); void send_wmclass(Ghandles * g, XID window, int ignore_fail); @@ -147,6 +274,69 @@ void process_xevent_damage(Ghandles * g, XID window, write_message(g->vchan, hdr, mx); } +void send_cursor(Ghandles *g, XID window, uint32_t cursor_id) +{ + struct msg_hdr hdr; + struct msg_cursor msg; + + hdr.type = MSG_CURSOR; + hdr.window = window; + msg.cursor = cursor_id; + write_message(g->vchan, hdr, msg); +} + +uint32_t find_cursor(Ghandles *g, Atom atom) +{ + char *name; + int i; + uint32_t cursor_id = CURSOR_NONE; + + if (atom == None) + return CURSOR_NONE; + + name = XGetAtomName(g->display, atom); + if (!name) + return CURSOR_NONE; + + for (i = 0; supported_cursors[i].name; i++) { + if (strcmp(supported_cursors[i].name, name) == 0) { + cursor_id = supported_cursors[i].cursor_id; + assert(cursor_id < CURSOR_X11_MAX); + break; + } + } + + free(name); + + return cursor_id; +} + +void process_xevent_cursor(Ghandles *g, XFixesCursorNotifyEvent *ev) +{ + if (ev->subtype == XFixesDisplayCursorNotify) { + /* The event from XFixes specifies only the root window, so we need to + * find out the window under pointer. + */ + Window root, window_under_pointer; + int root_x, root_y, win_x, win_y; + unsigned int mask; + Bool ret; + int cursor_id; + + ret = XQueryPointer(g->display, ev->window, &root, + &window_under_pointer, + &root_x, &root_y, &win_x, &win_y, &mask); + if (!ret || window_under_pointer == None) + return; + + if (!list_lookup(windows_list, window_under_pointer)) + return; + + cursor_id = find_cursor(g, ev->cursor_name); + send_cursor(g, window_under_pointer, cursor_id); + } +} + void process_xevent_createnotify(Ghandles * g, XCreateWindowEvent * ev) { struct msg_hdr hdr; @@ -1108,6 +1298,10 @@ void process_xevent(Ghandles * g) dev->area.width, dev->area.height); // fprintf(stderr, "@"); + } else if (event_buffer.type == (xfixes_event + XFixesCursorNotify)) { + process_xevent_cursor( + g, + (XFixesCursorNotifyEvent *) &event_buffer); } else if (g->log_level > 1) fprintf(stderr, "#"); } @@ -2080,6 +2274,14 @@ int main(int argc, char **argv) perror("XDamageQueryExtension"); exit(1); } + /* Use XFixes to handle cursor shape. */ + if (XFixesQueryExtension( + g.display, &xfixes_event, &xfixes_error)) { + for (i = 0; i < ScreenCount(g.display); i++) + XFixesSelectCursorInput(g.display, RootWindow(g.display, i), + XFixesDisplayCursorNotifyMask); + } else + fprintf(stderr, "XFixes not available, cursor shape handling off"); XAutoRepeatOff(g.display); signal(SIGCHLD, SIG_IGN); signal(SIGTERM, handle_sigterm); diff --git a/rpm_spec/gui-agent.spec.in b/rpm_spec/gui-agent.spec.in index c09bbee8..136e061b 100644 --- a/rpm_spec/gui-agent.spec.in +++ b/rpm_spec/gui-agent.spec.in @@ -42,6 +42,7 @@ BuildRequires: gcc BuildRequires: libX11-devel BuildRequires: libXcomposite-devel BuildRequires: libXdamage-devel +BuildRequires: libXfixes-devel BuildRequires: libXt-devel BuildRequires: libtool-ltdl-devel BuildRequires: pulseaudio-libs-devel >= 0.9.21, pulseaudio-libs-devel <= 13.0 @@ -53,6 +54,7 @@ BuildRequires: qubes-db-devel BuildRequires: xen-devel Requires: Xorg %(xserver-sdk-abi-requires ansic) Requires: Xorg %(xserver-sdk-abi-requires videodrv) +Requires: libXfixes Requires: qubes-core-vm >= 3.0.14 Requires: xorg-x11-xinit Requires: qubes-libvchan-@BACKEND_VMM@