diff --git a/gui-agent/Makefile b/gui-agent/Makefile index 17f9fb6e..717b842f 100644 --- a/gui-agent/Makefile +++ b/gui-agent/Makefile @@ -23,9 +23,9 @@ CC ?= gcc CFLAGS += -I../include/ `pkg-config --cflags vchan` -g -Wall -Wextra -Werror -fPIC \ -Wmissing-prototypes -Wstrict-prototypes -Wold-style-declaration \ -Wold-style-definition -OBJS = vmside.o txrx-vchan.o error.o list.o encoding.o +OBJS = vmside.o txrx-vchan.o error.o list.o encoding.o video-ext-client.o LIBS = -lX11 -lXdamage -lXcomposite -lXfixes `pkg-config --libs vchan` -lqubesdb \ - -lunistring + -lunistring -lXext all: qubes-gui qubes-gui-runuser diff --git a/gui-agent/video-ext-client.c b/gui-agent/video-ext-client.c new file mode 100644 index 00000000..f489a403 --- /dev/null +++ b/gui-agent/video-ext-client.c @@ -0,0 +1,117 @@ +/* + * The Qubes OS Project, https://www.qubes-os.org/ + * + * Copyright (C) 2025 Simon Gaiser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "video-ext-client.h" + +#include +#include +#include + +static int close_display(Display *dpy, XExtCodes *codes); +static Bool wire_to_event(Display *dpy, XEvent *libEv, xEvent *wireEv); + +static XExtensionHooks ext_hooks = { + NULL, // create_gc + NULL, // copy_gc + NULL, // flush_gc + NULL, // free_gc + NULL, // create_font + NULL, // free_font + close_display, // close_display + wire_to_event, // wire_to_event + NULL, // event_to_wire (we don't need SendEvent) + NULL, // error + NULL, // error_string +}; + +static XExtensionInfo _ext_info; +static XExtensionInfo *ext_info = &_ext_info; +static const char * ext_name = QVE_NAME; + +#define QVECheckExtension(dpy, info, val) \ + XextCheckExtension(dpy, info, ext_name, val) + +static XEXT_GENERATE_FIND_DISPLAY(find_display, + ext_info, + ext_name, + &ext_hooks, + QVENumberEvents, + NULL); + +static XEXT_GENERATE_CLOSE_DISPLAY(close_display, ext_info); + +static Bool +wire_to_event(Display *dpy, XEvent *libEv, xEvent *wireEv) +{ + XExtDisplayInfo *info = find_display(dpy); + + QVECheckExtension(dpy, info, False); + + BYTE type = wireEv->u.u.type; + if ((type & 0x7f) != info->codes->first_event + QVEWindowRealized) { + return False; + } + + XQVEWindowRealizedEvent *qLibEv = (XQVEWindowRealizedEvent *)libEv; + xQVEWindowRealizedEvent *qWireEv = (xQVEWindowRealizedEvent *)wireEv; + + qLibEv->type = type & 0x7f; + qLibEv->serial = _XSetLastRequestRead(dpy, (xGenericReply *)wireEv); + qLibEv->send_event = (type & 0x80) != 0; + qLibEv->display = dpy; + qLibEv->window = qWireEv->window; + qLibEv->unrealized = + (qWireEv->detail & QVEWindowRealizedDetailUnrealized) != 0; + + return True; +} + +Bool +XQVEQueryExtension(Display *dpy, int *event_base) +{ + XExtDisplayInfo *info = find_display(dpy); + + if (!XextHasExtension(info)) { + return False; + } + + *event_base = info->codes->first_event; + return True; +} + +Bool +XQVERegister(Display *dpy) +{ + XExtDisplayInfo *info = find_display(dpy); + + QVECheckExtension(dpy, info, False); + + LockDisplay(dpy); + xQVEReq *req; +#define X_QVE X_QVERegister + GetReq(QVE, req); +#undef X_QVE + req->reqType = info->codes->major_opcode; + req->QVEReqType = X_QVERegister; + UnlockDisplay(dpy); + SyncHandle(); + + return True; +} diff --git a/gui-agent/video-ext-client.h b/gui-agent/video-ext-client.h new file mode 100644 index 00000000..a0bb76ab --- /dev/null +++ b/gui-agent/video-ext-client.h @@ -0,0 +1,36 @@ +/* + * The Qubes OS Project, https://www.qubes-os.org/ + * + * Copyright (C) 2025 Simon Gaiser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "qubes-video-ext.h" +#include + +typedef struct { + int type; + unsigned long serial; + Bool send_event; + Display *display; + Window window; + Bool unrealized; +} XQVEWindowRealizedEvent; + +Bool XQVERegister(Display *dpy); +Bool XQVEQueryExtension(Display *dpy, int *event_base); diff --git a/gui-agent/vmside.c b/gui-agent/vmside.c index 0bb51bb9..e1d015af 100644 --- a/gui-agent/vmside.c +++ b/gui-agent/vmside.c @@ -51,6 +51,8 @@ #include "list.h" #include "error.h" #include "encoding.h" +#include "unix-addr.h" +#include "video-ext-client.h" #include #include #include "unistr.h" @@ -91,6 +93,7 @@ static int damage_event, damage_error; static int xfixes_event, xfixes_error; +static int qve_event; /* from gui-common/error.c */ extern int print_x11_errors; @@ -147,7 +150,8 @@ struct window_data { int input_hint; /* the window should get input focus - False=Never */ int support_delete_window; int support_take_focus; - int window_dump_pending; /* send MSG_WINDOW_DUMP at next damage notification */ + int realized; + int input_only; }; struct embeder_data { @@ -158,14 +162,38 @@ static struct genlist *windows_list; static struct genlist *embeder_list; static Ghandles *ghandles_for_vchan_reinitialize; -#define SKIP_NONMANAGED_WINDOW do { \ - if (!list_lookup(windows_list, window)) { \ - if (g->log_level > 0) \ - fprintf(stderr, "Skipping unmanaged window 0x%x\n", \ - (int) window); \ - return; \ - } \ -} while (0) +static void log_unmanaged_window(Ghandles *g, const char *context, XID window) { + if (g->log_level > 0) { + fprintf(stderr, + "%s: window 0x%lx not managed\n", + context, window); + } +} + +static struct genlist *lookup_window( + Ghandles *g, + struct genlist *list, + XID window, + const char *log_context) +{ + struct genlist *l = list_lookup(list, window); + if (l == NULL) { + if (log_context != NULL) { + log_unmanaged_window(g, log_context, window); + } + return NULL; + } + + if (l->data == NULL) { + // Should be unreachable. + fprintf(stderr, + "Error: list entry (for window 0x%lx) without data found!\n", + window); + exit(1); + } + + return l; +} /* Cursor name translation. See X11/cursorfont.h. */ @@ -304,11 +332,11 @@ static void send_event(Ghandles * g, const struct input_event *iev) { } g->created_input_device = 0; } - + // send syn const struct input_event syn = {.type = EV_SYN, .code = 0, .value = 0}; status = write(g->uinput_fd, &(syn), sizeof(struct input_event)); - + if ( status < 0 ) { if (g->log_level > 0) { fprintf(stderr, "writing SYN failed, falling back to xdriver. WRITE ERROR CODE: %d\n", status); @@ -321,7 +349,8 @@ static void send_event(Ghandles * g, const struct input_event *iev) { static void send_wmname(Ghandles * g, XID window); static void send_wmnormalhints(Ghandles * g, XID window, int ignore_fail); static void send_wmclass(Ghandles * g, XID window, int ignore_fail); -static void send_pixmap_grant_refs(Ghandles * g, XID window); +static void send_pixmap_grant_refs(Ghandles * g, XID window, + struct window_data * wd); static void retrieve_wmhints(Ghandles * g, XID window, int ignore_fail); static void retrieve_wmprotocols(Ghandles * g, XID window, int ignore_fail); @@ -330,19 +359,10 @@ static void process_xevent_damage(Ghandles * g, XID window, { struct msg_shmimage mx; struct msg_hdr hdr; - struct genlist *l; - struct window_data *wd; - l = list_lookup(windows_list, window); - if (!l) + if (!lookup_window(g, windows_list, window, __func__)) return; - wd = l->data; - if (wd->window_dump_pending) { - send_pixmap_grant_refs(g, window); - wd->window_dump_pending = False; - } - hdr.type = MSG_SHMIMAGE; hdr.window = window; mx.x = x; @@ -352,6 +372,26 @@ static void process_xevent_damage(Ghandles * g, XID window, write_message(g->vchan, hdr, mx); } +static void process_xevent_realized(Ghandles *g, XQVEWindowRealizedEvent *ev) +{ + struct genlist *l; + struct window_data *wd; + + l = lookup_window(g, windows_list, ev->window, __func__); + if (!l) { + return; + } + wd = l->data; + + wd->realized = !ev->unrealized; + + if (g->log_level > 1) { + fprintf(stderr, "window 0x%lx realized=%d\n", ev->window, wd->realized); + } + + send_pixmap_grant_refs(g, ev->window, wd); +} + static void send_cursor(Ghandles *g, XID window, uint32_t cursor) { struct msg_hdr hdr; @@ -409,7 +449,7 @@ static void process_xevent_cursor(Ghandles *g, XFixesCursorNotifyEvent *ev) if (!ret || window_under_pointer == None) return; - if (!list_lookup(windows_list, window_under_pointer)) + if (!lookup_window(g, windows_list, window_under_pointer, __func__)) return; cursor = find_cursor(g, ev->cursor_name); @@ -427,41 +467,37 @@ static void process_xevent_createnotify(Ghandles * g, XCreateWindowEvent * ev) int ret; ret = XGetWindowAttributes(g->display, ev->window, &attr); if (ret != 1) { - fprintf(stderr, "XGetWindowAttributes for 0x%x failed in " - "handle_create, ret=0x%x\n", (int) ev->window, - ret); + fprintf(stderr, "XGetWindowAttributes for 0x%lx failed in " + "handle_create, ret=0x%x\n", ev->window, ret); return; } if (g->log_level > 0) - fprintf(stderr, "Create for 0x%x class 0x%x\n", - (int) ev->window, attr.class); + fprintf(stderr, "Create for 0x%lx class 0x%x\n", + ev->window, attr.class); if (list_lookup(windows_list, ev->window)) { - fprintf(stderr, "CREATE for already existing 0x%x\n", - (int) ev->window); + fprintf(stderr, "CREATE for already existing 0x%lx\n", ev->window); return; } if (ev->parent != g->root_win) { /* GUI daemon no longer supports this */ fprintf(stderr, - "CREATE with non-root parent window 0x%x\n", - (unsigned int)ev->parent); + "CREATE with non-root parent window 0x%lx\n", ev->parent); return; } if (list_lookup(embeder_list, ev->window)) { /* ignore CreateNotify for embeder window */ if (g->log_level > 1) - fprintf(stderr, "CREATE for embeder 0x%x\n", - (int) ev->window); + fprintf(stderr, "CREATE for embeder 0x%lx\n", ev->window); return; } /* Initialize window_data structure */ wd = (struct window_data*)malloc(sizeof(struct window_data)); if (!wd) { - fprintf(stderr, "OUT OF MEMORY\n"); + fprintf(stderr, "%s: OUT OF MEMORY\n", __func__); return; } /* Default values for window_data. By default, window should receive InputFocus events */ @@ -469,14 +505,15 @@ static void process_xevent_createnotify(Ghandles * g, XCreateWindowEvent * ev) wd->input_hint = True; wd->support_delete_window = False; wd->support_take_focus = False; - wd->window_dump_pending = False; + wd->realized = False; + wd->input_only = attr.class == InputOnly; list_insert(windows_list, ev->window, wd); if (attr.border_width > 0) { XSetWindowBorderWidth(g->display, ev->window, 0); } - if (attr.class != InputOnly) + if (!wd->input_only) XDamageCreate(g->display, ev->window, XDamageReportRawRectangles); // the following hopefully avoids missed damage events @@ -514,11 +551,12 @@ static void feed_xdriver(Ghandles * g, int type, int arg1, int arg2) ret = read(g->xserver_fd, &ans, 1); if (ret != 1 || ans != '0') { perror("unix read"); - err(1, "read returned %zd, char read=0x%x\n", ret, (int) ans); + err(1, "read returned %zd, char read=0x%hhx\n", ret, ans); } } -void send_pixmap_grant_refs(Ghandles * g, XID window) +static void send_pixmap_grant_refs(Ghandles * g, XID window, + struct window_data * wd) { struct msg_hdr hdr; uint8_t *wd_msg_buf; @@ -526,6 +564,11 @@ void send_pixmap_grant_refs(Ghandles * g, XID window) size_t rcvd; int ret; + if (wd->input_only || !wd->realized) { + // see comment in dump_window_grant_refs in the input driver + return; + } + feed_xdriver(g, 'W', (int) window, 0); if (read(g->xserver_fd, &wd_msg_len, sizeof(wd_msg_len)) != sizeof(wd_msg_len)) err(1, "unix read wd_msg_len"); @@ -678,33 +721,37 @@ void send_wmname(Ghandles * g, XID window) write_message(g->vchan, hdr, msg); } -/* Retrieve the supported WM Protocols - We don't forward the info to dom0 as we only need specific client protocols - */ +/* + * Retrieve the supported WM Protocols + * + * We don't forward the info to dom0 as we only need specific client protocols. + */ void retrieve_wmprotocols(Ghandles * g, XID window, int ignore_fail) { int nitems; Atom *supported_protocols; int i; struct genlist *l; + struct window_data *wd; - if (!((l=list_lookup(windows_list, window)) && (l->data))) { - fprintf(stderr, "ERROR retrieve_wmprotocols: Window 0x%x data not initialized\n", (int)window); + l = lookup_window(g, windows_list, window, __func__); + if (!l) { return; } + wd = l->data; if (XGetWMProtocols(g->display, window, &supported_protocols, &nitems) == 1) { for (i=0; i < nitems; i++) { if (supported_protocols[i] == g->wm_take_focus) { if (g->log_level > 1) - fprintf(stderr, "Protocol take_focus supported for Window 0x%x\n", (int)window); + fprintf(stderr, "Protocol take_focus supported for Window 0x%lx\n", window); - ((struct window_data*)l->data)->support_take_focus = True; + wd->support_take_focus = True; } else if (supported_protocols[i] == g->wmDeleteWindow) { if (g->log_level > 1) - fprintf(stderr, "Protocol delete_window supported for Window 0x%x\n", (int)window); + fprintf(stderr, "Protocol delete_window supported for Window 0x%lx\n", window); - ((struct window_data*)l->data)->support_delete_window = True; + wd->support_delete_window = True; } } } else { @@ -716,18 +763,23 @@ void retrieve_wmprotocols(Ghandles * g, XID window, int ignore_fail) } -/* Retrieve the 'real' WMHints. - We don't forward the info to dom0 as we only need InputHint and dom0 doesn't care about it - */ +/* + * Retrieve the 'real' WMHints + * + * We don't forward the info to dom0 as we only need InputHint and dom0 doesn't + * care about it. + */ void retrieve_wmhints(Ghandles * g, XID window, int ignore_fail) { XWMHints *wm_hints; struct genlist *l; + struct window_data *wd; - if (!((l=list_lookup(windows_list, window)) && (l->data))) { - fprintf(stderr, "ERROR retrieve_wmhints: Window 0x%x data not initialized\n", (int)window); + l = lookup_window(g, windows_list, window, __func__); + if (!l) { return; } + wd = l->data; if (!(wm_hints = XGetWMHints(g->display, window))) { if (!ignore_fail) @@ -736,15 +788,15 @@ void retrieve_wmhints(Ghandles * g, XID window, int ignore_fail) } if (wm_hints->flags & InputHint) { - ((struct window_data*)l->data)->input_hint = wm_hints->input; + wd->input_hint = wm_hints->input; if (g->log_level > 1) - fprintf(stderr, "Received input hint 0x%x for Window 0x%x\n", wm_hints->input, (int)window); + fprintf(stderr, "Received input hint 0x%x for Window 0x%lx\n", wm_hints->input, window); } else { // Default value if (g->log_level > 1) - fprintf(stderr, "Received WMHints without input hint set for Window 0x%x\n", (int)window); - ((struct window_data*)l->data)->input_hint = True; + fprintf(stderr, "Received WMHints without input hint set for Window 0x%lx\n", window); + wd->input_hint = True; } XFree(wm_hints); } @@ -867,14 +919,13 @@ static void process_xevent_map(Ghandles * g, XID window) struct msg_hdr hdr; struct msg_map_info map_info; Window transient; - struct window_data *wd; - SKIP_NONMANAGED_WINDOW; - wd = list_lookup(windows_list, window)->data; + if (!lookup_window(g, windows_list, window, __func__)) { + return; + } if (g->log_level > 1) - fprintf(stderr, "MAP for window 0x%x\n", (int)window); - wd->window_dump_pending = True; + fprintf(stderr, "MAP for window 0x%lx\n", window); send_window_state(g, window); XGetWindowAttributes(g->display, window, &attr); if (XGetTransientForHint(g->display, window, &transient)) @@ -898,10 +949,13 @@ static void process_xevent_map(Ghandles * g, XID window) static void process_xevent_unmap(Ghandles * g, XID window) { struct msg_hdr hdr; - SKIP_NONMANAGED_WINDOW; + + if (!lookup_window(g, windows_list, window, __func__)) { + return; + } if (g->log_level > 1) - fprintf(stderr, "UNMAP for window 0x%x\n", (int)window); + fprintf(stderr, "UNMAP for window 0x%lx\n", window); hdr.type = MSG_UNMAP; hdr.window = window; hdr.untrusted_len = 0; @@ -914,28 +968,31 @@ static void process_xevent_destroy(Ghandles * g, XID window) { struct msg_hdr hdr; struct genlist *l; - /* embeders are not manged windows, so must be handled before SKIP_NONMANAGED_WINDOW */ - if ((l = list_lookup(embeder_list, window))) { - if (l->data) { - free(l->data); - } + struct window_data *wd; + + /* embeders are not manged windows, so must be handled first */ + l = lookup_window(g, embeder_list, window, NULL); + if (l) { + free(l->data); list_remove(l); + return; } - SKIP_NONMANAGED_WINDOW; + l = lookup_window(g, windows_list, window, __func__); + if (!l) { + return; + } + wd = l->data; if (g->log_level > 0) - fprintf(stderr, "handle destroy 0x%x\n", (int) window); + fprintf(stderr, "handle destroy 0x%lx\n", window); hdr.type = MSG_DESTROY; hdr.window = window; hdr.untrusted_len = 0; write_struct(g->vchan, hdr); - l = list_lookup(windows_list, window); - if (l->data) { - if (((struct window_data*)l->data)->is_docked) { - XDestroyWindow(g->display, ((struct window_data*)l->data)->embeder); - } - free(l->data); + if (wd->is_docked) { + XDestroyWindow(g->display, wd->embeder); } + free(l->data); list_remove(l); } @@ -945,40 +1002,51 @@ static void process_xevent_configure(Ghandles * g, XID window, struct msg_hdr hdr; struct msg_configure conf; struct genlist *l; - /* SKIP_NONMANAGED_WINDOW; */ - if (!(l=list_lookup(windows_list, window))) { + struct window_data *wd = NULL; + struct window_data *wd_msg; + + l = lookup_window(g, windows_list, window, NULL); + if (l) { + wd = l->data; + wd_msg = wd; + } else { /* if not real managed window, check if this is embeder for another window */ - struct genlist *e; - if ((e=list_lookup(embeder_list, window))) { + struct genlist *e = lookup_window(g, embeder_list, window, NULL); + if (e) { + struct genlist *i; window = ((struct embeder_data*)e->data)->icon_window; - if (!list_lookup(windows_list, window)) - /* probably icon window have just destroyed, so ignore message */ - /* "l" not updated intentionally - when configure notify comes - * from the embeder, it should be passed to dom0 (in most cases as - * ACK for earlier configure request) */ + i = lookup_window(g, windows_list, window, NULL); + if (!i) + /* probably icon window has just been destroyed, so ignore + * message */ return; + /* l and wd not updated intentionally - when configure notify comes + * from the embeder, it should be passed to dom0 (in most cases as + * ACK for earlier configure request) */ + wd_msg = i->data; } else { /* ignore not managed windows */ + log_unmanaged_window(g, __func__, window); return; } } if (g->log_level > 1) fprintf(stderr, - "handle configure event 0x%x w=%d h=%d ovr=%d\n", - (int) window, ev->width, ev->height, - (int) ev->override_redirect); - if (l && l->data && ((struct window_data*)l->data)->is_docked) { + "handle configure event 0x%lx w=%d h=%d ovr=%d\n", + window, ev->width, ev->height, + ev->override_redirect); + if (wd && wd->is_docked) { /* for docked icon, ensure that it fills embeder window; don't send any * message to dom0 - it will be done for embeder itself*/ XWindowAttributes attr; int ret; - ret = XGetWindowAttributes(g->display, ((struct window_data*)l->data)->embeder, &attr); + ret = XGetWindowAttributes(g->display, wd->embeder, &attr); if (ret != 1) { fprintf(stderr, - "XGetWindowAttributes for 0x%x failed in " - "handle_xevent_configure, ret=0x%x\n", (int) ((struct window_data*)l->data)->embeder, ret); + "XGetWindowAttributes for 0x%lx failed in " + "handle_xevent_configure, ret=0x%x\n", wd->embeder, ret); return; } if (ev->x != 0 || ev->y != 0 || ev->width != attr.width || ev->height != attr.height) { @@ -999,7 +1067,7 @@ static void process_xevent_configure(Ghandles * g, XID window, conf.height = ev->height; conf.override_redirect = ev->override_redirect; write_message(g->vchan, hdr, conf); - send_pixmap_grant_refs(g, window); + send_pixmap_grant_refs(g, window, wd_msg); } static void send_clipboard_data(libvchan_t *vchan, XID window, char *data, uint32_t len, int protocol_version) @@ -1033,8 +1101,8 @@ static void handle_targets_list(Ghandles * g, unsigned char *data, int len) if (atoms[i] == g->utf8_string_atom) have_utf8 = 1; if (g->log_level > 1) - fprintf(stderr, "supported 0x%x %s\n", - (int) atoms[i], XGetAtomName(g->display, + fprintf(stderr, "supported 0x%lx %s\n", + atoms[i], XGetAtomName(g->display, atoms[i])); } XConvertSelection(g->display, g->clipboard, @@ -1158,9 +1226,8 @@ static void process_xevent_selection_req(Ghandles * g, if (resp.property == None) fprintf(stderr, - "Not supported selection_req target 0x%x %s\n", - (int) req->target, XGetAtomName(g->display, - req->target)); + "Not supported selection_req target 0x%lx %s\n", + req->target, XGetAtomName(g->display, req->target)); resp.type = SelectionNotify; resp.display = req->display; resp.requestor = req->requestor; @@ -1173,12 +1240,20 @@ static void process_xevent_selection_req(Ghandles * g, static void process_xevent_property(Ghandles * g, XID window, XPropertyEvent * ev) { + struct genlist *l; + struct window_data *wd; + g->time = ev->time; - SKIP_NONMANAGED_WINDOW; + + l = lookup_window(g, windows_list, window, __func__); + if (!l) { + return; + } + wd = l->data; + if (g->log_level > 1) - fprintf(stderr, "handle property %s for window 0x%x\n", - XGetAtomName(g->display, ev->atom), - (int) ev->window); + fprintf(stderr, "handle property %s for window 0x%lx\n", + XGetAtomName(g->display, ev->atom), ev->window); if (ev->atom == XA_WM_NAME) send_wmname(g, window); else if (ev->atom == g->net_wm_name) @@ -1192,13 +1267,12 @@ static void process_xevent_property(Ghandles * g, XID window, XPropertyEvent * e else if (ev->atom == g->wmProtocols) retrieve_wmprotocols(g,window, 0); else if (ev->atom == g->xembed_info) { - struct genlist *l = list_lookup(windows_list, window); Atom act_type; unsigned long nitems, bytesafter; unsigned char *data; int ret, act_fmt; - if (!l->data || !((struct window_data*)l->data)->is_docked) + if (wd->is_docked) /* ignore _XEMBED_INFO change on non-docked windows */ return; ret = XGetWindowProperty(g->display, window, g->xembed_info, 0, 2, False, @@ -1218,9 +1292,8 @@ static void process_xevent_property(Ghandles * g, XID window, XPropertyEvent * e static void process_xevent_message(Ghandles * g, XClientMessageEvent * ev) { if (g->log_level > 1) - fprintf(stderr, "handle message %s to window 0x%x\n", - XGetAtomName(g->display, ev->message_type), - (int) ev->window); + fprintf(stderr, "handle message %s to window 0x%lx\n", + XGetAtomName(g->display, ev->message_type), ev->window); if (ev->message_type == g->tray_opcode) { XClientMessageEvent resp; Window w; @@ -1239,14 +1312,14 @@ static void process_xevent_message(Ghandles * g, XClientMessageEvent * ev) case SYSTEM_TRAY_REQUEST_DOCK: w = ev->data.l[2]; - if (!(l=list_lookup(windows_list, w))) { - fprintf(stderr, "ERROR process_xevent_message: Window 0x%x not initialized\n", (int)w); + l = lookup_window(g, windows_list, w, "SYSTEM_TRAY_REQUEST_DOCK"); + if (!l) { return; } + wd = l->data; + if (g->log_level > 0) - fprintf(stderr, - "tray request dock for window 0x%x\n", - (int) w); + fprintf(stderr, "tray request dock for window 0x%lx\n", w); ret = XGetWindowProperty(g->display, w, g->xembed_info, 0, 2, False, g->xembed_info, &act_type, &act_fmt, &nitems, &bytesafter, &data); @@ -1264,11 +1337,6 @@ static void process_xevent_message(Ghandles * g, XClientMessageEvent * ev) if (ret == Success && nitems > 0) XFree(data); - if (!(l->data)) { - fprintf(stderr, "ERROR process_xevent_message: Window 0x%x data not initialized\n", (int)w); - return; - } - wd = (struct window_data*)(l->data); /* TODO: error checking */ wd->embeder = XCreateSimpleWindow(g->display, g->root_win, 0, 0, 32, 32, /* default icon size, will be changed by dom0 */ @@ -1278,13 +1346,11 @@ static void process_xevent_message(Ghandles * g, XClientMessageEvent * ev) g->screen)); wd->is_docked=True; if (g->log_level > 1) - fprintf(stderr, - " created embeder 0x%x\n", - (int) wd->embeder); + fprintf(stderr, "created embeder 0x%lx\n", wd->embeder); XSelectInput(g->display, wd->embeder, SubstructureNotifyMask); ed = (struct embeder_data*)malloc(sizeof(struct embeder_data)); if (!ed) { - fprintf(stderr, "OUT OF MEMORY\n"); + fprintf(stderr, "%s: OUT OF MEMORY\n", __func__); return; } ed->icon_window = w; @@ -1293,9 +1359,8 @@ static void process_xevent_message(Ghandles * g, XClientMessageEvent * ev) ret = XReparentWindow(g->display, w, wd->embeder, 0, 0); if (ret != 1) { fprintf(stderr, - "XReparentWindow for 0x%x failed in " - "handle_dock, ret=0x%x\n", (int) w, - ret); + "XReparentWindow for 0x%lx failed in " + "handle_dock, ret=0x%x\n", w, ret); return; } @@ -1335,8 +1400,8 @@ static void process_xevent_message(Ghandles * g, XClientMessageEvent * ev) struct msg_hdr hdr; struct msg_window_flags msg; - /* SKIP_NONMANAGED_WINDOW */ - if (!list_lookup(windows_list, ev->window)) return; + if (!lookup_window(g, windows_list, ev->window, "_NET_WM_STATE")) + return; msg.flags_set = 0; msg.flags_unset = 0; @@ -1347,12 +1412,12 @@ static void process_xevent_message(Ghandles * g, XClientMessageEvent * ev) msg.flags_set |= flags_from_atom(g, ev->data.l[1]); msg.flags_set |= flags_from_atom(g, ev->data.l[2]); } else if (ev->data.l[0] == 2) { /* toggle property */ - fprintf(stderr, "toggle window 0x%x property %s not supported, " - "please report it with the application name\n", (int) ev->window, + fprintf(stderr, "toggle window 0x%lx property %s not supported, " + "please report it with the application name\n", ev->window, XGetAtomName(g->display, ev->data.l[1])); } else { - fprintf(stderr, "invalid window state command (%ld) for window 0x%x" - "report with application name\n", ev->data.l[0], (int) ev->window); + fprintf(stderr, "invalid window state command (%ld) for window 0x%lx" + "report with application name\n", ev->data.l[0], ev->window); } hdr.window = ev->window; hdr.type = MSG_WINDOW_FLAGS; @@ -1409,22 +1474,32 @@ static void process_xevent(Ghandles * g) if (event_buffer.type == (damage_event + XDamageNotify)) { dev = (XDamageNotifyEvent *) & event_buffer; g->time = dev->timestamp; - // fprintf(stderr, "x=%hd y=%hd gx=%hd gy=%hd w=%hd h=%hd\n", - // dev->area.x, dev->area.y, dev->geometry.x, dev->geometry.y, dev->area.width, dev->area.height); + if (g->log_level > 1) { + fprintf(stderr, + "DamageNotify x=%hd y=%hd gx=%hd gy=%hd w=%hd h=%hd\n", + dev->area.x, dev->area.y, + dev->geometry.x, dev->geometry.y, + dev->area.width, dev->area.height); + } process_xevent_damage(g, dev->drawable, dev->area.x, dev->area.y, 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, "#"); + } else if (event_buffer.type == (qve_event + QVEWindowRealized)) { + process_xevent_realized( + g, + (XQVEWindowRealizedEvent *)&event_buffer); + } else if (g->log_level > 1) { + fprintf(stderr, + "%s: unhandled event of type %d\n", + __func__, event_buffer.type); + } } - } /* return 1 if info sent, 0 otherwise */ @@ -1446,16 +1521,14 @@ static int send_full_window_info(Ghandles *g, XID w, struct window_data *wd) const Window window_to_query = wd->is_docked ? wd->embeder : w; ret = XGetWindowAttributes(g->display, window_to_query, &attr); if (ret != 1) { - fprintf(stderr, "XGetWindowAttributes for 0x%x failed in " - "send_window_state, ret=0x%x\n", (int) w, - ret); + fprintf(stderr, "XGetWindowAttributes for 0x%lx failed in " + "send_window_state, ret=0x%x\n", w, ret); return 0; } ret = XQueryTree(g->display, window_to_query, &root, &parent, &children_list, &children_count); if (ret != 1) { - fprintf(stderr, "XQueryTree for 0x%x failed in " - "send_window_state, ret=0x%x\n", (int) w, - ret); + fprintf(stderr, "XQueryTree for 0x%lx failed in " + "send_window_state, ret=0x%x\n", w, ret); return 0; } if (children_list) @@ -1498,7 +1571,22 @@ static int send_full_window_info(Ghandles *g, XID w, struct window_data *wd) conf.height = attr.height; conf.override_redirect = attr.override_redirect; write_message(g->vchan, hdr, conf); - send_pixmap_grant_refs(g, w); + + int realized = attr.map_state == IsViewable; + if (realized != wd->realized) { + fprintf(stderr, "Error: Internal \"realized\" state does not match " + "X server state\n"); + wd->realized = realized; + } + + int input_only = attr.class == InputOnly; + if (input_only != wd->input_only) { + fprintf(stderr, "Error: Internal \"input_only\" state does not match " + "X server state\n"); + wd->input_only = input_only; + } + + send_pixmap_grant_refs(g, w, wd); send_wmclass(g, w, 1); send_wmnormalhints(g, w, 1); @@ -1539,26 +1627,29 @@ static void send_all_windows_info(Ghandles *g) { static void wait_for_unix_socket(Ghandles *g) { struct sockaddr_un sockname, peer; - unsigned int addrlen; + socklen_t addrlen; int prev_umask; struct group *qubes_group; /* setup listening socket only once; in case of qubes_drv reconnections, * simply pickup next waiting connection there (using accept below) */ if (g->xserver_listen_fd == -1) { + addrlen = sockaddr_un_from_path(&sockname, SOCKET_ADDRESS); + if (addrlen == 0) { + fprintf(stderr, "invalid socket path: %s\n", SOCKET_ADDRESS); + exit(1); + } + unlink(SOCKET_ADDRESS); g->xserver_listen_fd = socket(AF_UNIX, SOCK_STREAM, 0); - memset(&sockname, 0, sizeof(sockname)); - sockname.sun_family = AF_UNIX; - memcpy(sockname.sun_path, SOCKET_ADDRESS, strlen(SOCKET_ADDRESS)); qubes_group = getgrnam("qubes"); if (qubes_group) prev_umask=umask(0007); else prev_umask=umask(0000); - if (bind(g->xserver_listen_fd, (struct sockaddr *) &sockname, sizeof(sockname)) == -1) { - printf("bind() failed\n"); + if (bind(g->xserver_listen_fd, (struct sockaddr *)&sockname, addrlen) == -1) { + fprintf(stderr, "bind() failed\n"); close(g->xserver_listen_fd); exit(1); } @@ -1601,7 +1692,7 @@ static void mkghandles(Ghandles * g) g->xserver_listen_fd = -1; g->xserver_fd = -1; - wait_for_unix_socket(g); // wait for Xorg qubes_drv to connect to us + wait_for_unix_socket(g); // wait for Xorg qubes_drv to connect to us do { g->display = XOpenDisplay(NULL); if (!g->display && errno != EAGAIN) { @@ -1612,8 +1703,8 @@ static void mkghandles(Ghandles * g) if (g->log_level > 0) fprintf(stderr, "Connection to local X server established.\n"); - g->screen = DefaultScreen(g->display); /* get CRT id number */ - g->root_win = RootWindow(g->display, g->screen); /* get default attributes */ + g->screen = DefaultScreen(g->display); /* get CRT id number */ + g->root_win = RootWindow(g->display, g->screen); /* get default attributes */ g->context = XCreateGC(g->display, g->root_win, 0, NULL); g->stub_win = XCreateSimpleWindow(g->display, g->root_win, 0, 0, 1, 1, @@ -1746,7 +1837,7 @@ static void handle_keypress(Ghandles * g, XID UNUSED(winid)) XFreeModifiermap(modmap); } } - + feed_xdriver(g, 'K', key.keycode, key.type == KeyPress ? 1 : 0); } else { int mod_mask; @@ -1755,7 +1846,7 @@ static void handle_keypress(Ghandles * g, XID UNUSED(winid)) iev.type = EV_KEY; XModifierKeymap *modmap; modmap = XGetModifierMapping(g->display); - + if (!modmap) { if (g->log_level > 0) fprintf(stderr, "failed to get modifier mapping\n"); @@ -1789,7 +1880,7 @@ static void handle_keypress(Ghandles * g, XID UNUSED(winid)) // update state for this modifier g->last_known_modifier_states ^= mod_mask; } - + // last modifier state was up, modifier has since been pressed down else if (!(g->last_known_modifier_states & mod_mask) && (key.state & mod_mask)) { iev.code = modmap->modifiermap[mod_index*modmap->max_keypermod] - 8; @@ -1802,7 +1893,7 @@ static void handle_keypress(Ghandles * g, XID UNUSED(winid)) } } } - + XFreeModifiermap(modmap); // caps lock needs to be excluded to not send down, up, down or down, up, up on a caps lock sync instead of down, up @@ -1811,27 +1902,32 @@ static void handle_keypress(Ghandles * g, XID UNUSED(winid)) iev.value = (key.type == KeyPress ? 1 : 0); send_event(g, &iev); } - + } } static void handle_button(Ghandles * g, XID winid) { struct msg_button key; - struct genlist *l = list_lookup(windows_list, winid); + struct genlist *l; + struct window_data *wd = NULL; + l = lookup_window(g, windows_list, winid, NULL); + if (l) { + wd = l->data; + } read_data(g->vchan, (char *) &key, sizeof(key)); - if (l && l->data && ((struct window_data*)l->data)->is_docked) { + if (wd && wd->is_docked) { /* get position of embeder, not icon itself*/ - winid = ((struct window_data*)l->data)->embeder; + winid = wd->embeder; XRaiseWindow(g->display, winid); } if (g->log_level > 1) fprintf(stderr, - "send buttonevent, win 0x%x type=%d button=%d\n", - (int) winid, key.type, key.button); + "send buttonevent, win 0x%lx type=%d button=%d\n", + winid, key.type, key.button); feed_xdriver(g, 'B', key.button, key.type == ButtonPress ? 1 : 0); } @@ -1841,18 +1937,24 @@ static void handle_motion(Ghandles * g, XID winid) // XMotionEvent event; XWindowAttributes attr; int ret; - struct genlist *l = list_lookup(windows_list, winid); + struct genlist *l; + struct window_data *wd = NULL; + + l = lookup_window(g, windows_list, winid, NULL); + if (l) { + wd = l->data; + } read_data(g->vchan, (char *) &key, sizeof(key)); - if (l && l->data && ((struct window_data*)l->data)->is_docked) { + if (wd && wd->is_docked) { /* get position of embeder, not icon itself*/ - winid = ((struct window_data*)l->data)->embeder; + winid = wd->embeder; } ret = XGetWindowAttributes(g->display, winid, &attr); if (ret != 1) { fprintf(stderr, - "XGetWindowAttributes for 0x%x failed in " - "do_button, ret=0x%x\n", (int) winid, ret); + "XGetWindowAttributes for 0x%lx failed in " + "do_button, ret=0x%x\n", winid, ret); return; } @@ -1866,13 +1968,19 @@ static void handle_crossing(Ghandles * g, XID winid) struct msg_crossing key; XWindowAttributes attr; int ret; - struct genlist *l = list_lookup(windows_list, winid); + struct genlist *l; + struct window_data *wd = NULL; + + l = lookup_window(g, windows_list, winid, NULL); + if (l) { + wd = l->data; + } /* we want to always get root window child (as this we get from * XQueryPointer and can compare to window_under_pointer), so for embeded * window get the embeder */ - if (l && l->data && ((struct window_data*)l->data)->is_docked) { - winid = ((struct window_data*)l->data)->embeder; + if (wd && wd->is_docked) { + winid = wd->embeder; } read_data(g->vchan, (char *) &key, sizeof(key)); @@ -1884,8 +1992,8 @@ static void handle_crossing(Ghandles * g, XID winid) ret = XGetWindowAttributes(g->display, winid, &attr); if (ret != 1) { fprintf(stderr, - "XGetWindowAttributes for 0x%x failed in " - "handle_crossing, ret=0x%x\n", (int) winid, ret); + "XGetWindowAttributes for 0x%lx failed in " + "handle_crossing, ret=0x%x\n", winid, ret); return; } @@ -1902,8 +2010,8 @@ static void handle_crossing(Ghandles * g, XID winid) &win_x, &win_y, &mask_return); if (ret != 1) { fprintf(stderr, - "XQueryPointer for 0x%x failed in " - "handle_crossing, ret=0x%x\n", (int) winid, + "XQueryPointer for 0x%lx failed in " + "handle_crossing, ret=0x%x\n", winid, ret); return; } @@ -1935,8 +2043,7 @@ static void take_focus(Ghandles * g, XID winid) ev.data.l[1] = g->time; XSendEvent(ev.display, ev.window, 1, 0, (XEvent *) & ev); if (g->log_level > 0) - fprintf(stderr, "WM_TAKE_FOCUS sent for 0x%x\n", - (int) winid); + fprintf(stderr, "WM_TAKE_FOCUS sent for 0x%lx\n", winid); } @@ -1944,6 +2051,7 @@ static void handle_focus(Ghandles * g, XID winid) { struct msg_focus key; struct genlist *l; + struct window_data *wd; int input_hint; int use_take_focus; @@ -1953,13 +2061,14 @@ static void handle_focus(Ghandles * g, XID winid) XRaiseWindow(g->display, winid); - 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); + l = lookup_window(g, windows_list, winid, "FocusIn"); + if (l) { + wd = l->data; + input_hint = wd->input_hint; + use_take_focus = wd->support_take_focus; + if (wd->is_docked) + XRaiseWindow(g->display, wd->embeder); } else { - fprintf(stderr, "WARNING handle_focus: FocusIn: Window 0x%x data not initialized\n", (int)winid); input_hint = True; use_take_focus = False; } @@ -1973,21 +2082,22 @@ static void handle_focus(Ghandles * g, XID winid) take_focus(g, winid); if (g->log_level > 1) - fprintf(stderr, "0x%x raised\n", (int) winid); + fprintf(stderr, "0x%lx raised\n", 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: FocusOut: Window 0x%x data not initialized\n", (int)winid); + l = lookup_window(g, windows_list, winid, "FocusOut"); + if (l) { + wd = l->data; + input_hint = wd->input_hint; + } else { input_hint = True; } if (input_hint) XSetInputFocus(g->display, None, RevertToParent, g->time); if (g->log_level > 1) - fprintf(stderr, "0x%x lost focus\n", (int) winid); + fprintf(stderr, "0x%lx lost focus\n", winid); } } @@ -2018,12 +2128,19 @@ static void handle_keymap_notify(Ghandles * g) static void handle_configure(Ghandles * g, XID winid) { struct msg_configure r; - struct genlist *l = list_lookup(windows_list, winid); + struct genlist *l; + struct window_data *wd = NULL; XWindowAttributes attr; + + l = lookup_window(g, windows_list, winid, __func__); + if (l) { + wd = l->data; + } + XGetWindowAttributes(g->display, winid, &attr); read_data(g->vchan, (char *) &r, sizeof(r)); - if (l && l->data && ((struct window_data*)l->data)->is_docked) { - XMoveResizeWindow(g->display, ((struct window_data*)l->data)->embeder, r.x, r.y, r.width, r.height); + if (wd && wd->is_docked) { + XMoveResizeWindow(g->display, wd->embeder, r.x, r.y, r.width, r.height); XMoveResizeWindow(g->display, winid, 0, 0, r.width, r.height); } else { XMoveResizeWindow(g->display, winid, r.x, r.y, r.width, r.height); @@ -2046,19 +2163,20 @@ static void handle_map(Ghandles * g, XID winid) CWOverrideRedirect, &attr); XMapWindow(g->display, winid); if (g->log_level > 1) - fprintf(stderr, "map msg for 0x%x\n", (int) winid); + fprintf(stderr, "map msg for 0x%lx\n", winid); } static void handle_close(Ghandles * g, XID winid) { struct genlist *l; + struct window_data *wd; int use_delete_window; - if ( (l=list_lookup(windows_list, winid)) && (l->data) ) { - use_delete_window = ((struct window_data*)l->data)->support_delete_window; + l = lookup_window(g, windows_list, winid, __func__); + if (l) { + wd = l->data; + use_delete_window = wd->support_delete_window; } else { - fprintf(stderr, "WARNING handle_close: Window 0x%x data not initialized\n", - (int)winid); use_delete_window = True; /* gentler, though it may be a no-op */ } @@ -2073,13 +2191,11 @@ static void handle_close(Ghandles * g, XID winid) ev.data.l[0] = g->wmDeleteWindow; XSendEvent(ev.display, ev.window, 1, 0, (XEvent *) & ev); if (g->log_level > 0) - fprintf(stderr, "wmDeleteWindow sent for 0x%x\n", - (int) winid); + fprintf(stderr, "wmDeleteWindow sent for 0x%lx\n", winid); } else { XKillClient(g->display, winid); if (g->log_level > 0) - fprintf(stderr, "XKillClient() called for 0x%x\n", - (int) winid); + fprintf(stderr, "XKillClient() called for 0x%lx\n", winid); } } @@ -2136,8 +2252,7 @@ static void handle_clipboard_req(Ghandles * g, XID winid) #endif owner = XGetSelectionOwner(g->display, Clp); if (g->log_level > 0) - fprintf(stderr, "clipboard req, owner=0x%x\n", - (int) owner); + fprintf(stderr, "clipboard req, owner=0x%lx\n", owner); if (owner == None) { send_clipboard_data(g->vchan, winid, NULL, 0, g->protocol_version); return; @@ -2433,17 +2548,17 @@ int main(int argc, char **argv) g.uinput_fd = open("/dev/uinput", O_WRONLY | O_NDELAY); if(g.uinput_fd < 0) { g.uinput_fd = open("/dev/input/uinput", O_WRONLY | O_NDELAY); - + if(g.uinput_fd < 0) { fprintf(stderr, "Couldn't open uinput, falling back to xdriver\n"); g.created_input_device = 0; } } } - + // input device creation if(g.created_input_device) { - + if (ioctl(g.uinput_fd, UI_SET_EVBIT, EV_SYN) < 0) { fprintf(stderr, "error setting EVBIT for EV_SYN, falling back to xdriver\n"); g.created_input_device = 0; @@ -2460,22 +2575,22 @@ int main(int argc, char **argv) fprintf(stderr, "Not able to set KEYBIT %d\n", i); } } - + struct uinput_setup usetup; memset(&usetup, 0, sizeof(usetup)); strcpy(usetup.name, "Qubes Virtual Input Device"); - + if(ioctl(g.uinput_fd, UI_DEV_SETUP, &usetup) < 0) { fprintf(stderr, "Input device setup failed, falling back to xdriver\n"); g.created_input_device = 0; } else { - + if(ioctl(g.uinput_fd, UI_DEV_CREATE) < 0) { fprintf(stderr, "Input device creation failed, falling back to xdriver\n"); g.created_input_device = 0; } } - + g.last_known_modifier_states = 0; } @@ -2543,6 +2658,16 @@ int main(int argc, char **argv) exit(1); } + if (!XQVEQueryExtension(g.display, &qve_event)) { + fprintf(stderr, QVE_NAME " is not available\n"); + exit(1); + } + + if (!XQVERegister(g.display)) { + fprintf(stderr, "Failed to register with " QVE_NAME "\n"); + exit(1); + } + /* Sort the cursors table for use with bsearch. */ qsort(supported_cursors, NUM_SUPPORTED_CURSORS, sizeof(supported_cursors[0]), diff --git a/include/qubes-video-ext.h b/include/qubes-video-ext.h new file mode 100644 index 00000000..ff972762 --- /dev/null +++ b/include/qubes-video-ext.h @@ -0,0 +1,64 @@ +/* + * The Qubes OS Project, https://www.qubes-os.org/ + * + * Copyright (C) 2025 Simon Gaiser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include +#include + +// This X extension is only for internal use. It's used to send custom events +// from the video driver to the agent. Since video driver and agent are updated +// in lockstep we don't need any version handling. + +#define QVE_NAME "_qubes-video-ext" + +#define X_QVERegister 0 +#define X_QVEUnregister 1 +#define XQVENumberRequests 2 + +#define QVEWindowRealized 0 +#define QVENumberEvents 1 + +typedef struct { + CARD8 reqType; + CARD8 QVEReqType; + CARD16 length; +} xQVEReq; + +#define sz_xQVEReq sizeof(xQVEReq) + +static_assert(sizeof(xQVEReq) >= sizeof(xReq)); + +#define QVEWindowRealizedDetailUnrealized 1 + +typedef struct { + BYTE type; + BYTE detail; + CARD16 sequenceNumber; + CARD32 window; + CARD32 pad1; + CARD32 pad2; + CARD32 pad3; + CARD32 pad4; + CARD32 pad5; + CARD32 pad6; +} xQVEWindowRealizedEvent; + +static_assert(sizeof(xQVEWindowRealizedEvent) == sizeof(xEvent)); diff --git a/include/unix-addr.h b/include/unix-addr.h new file mode 100644 index 00000000..d8f9af17 --- /dev/null +++ b/include/unix-addr.h @@ -0,0 +1,37 @@ +/* + * The Qubes OS Project, https://www.qubes-os.org/ + * + * Copyright (C) 2025 Simon Gaiser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include + +static inline socklen_t sockaddr_un_from_path(struct sockaddr_un *addr, char *path) +{ + size_t len; + + memset(addr, 0, sizeof(*addr)); + addr->sun_family = AF_UNIX; + len = strlen(path); + if (len == 0 || len > sizeof(addr->sun_path) - 1) { + return 0; + } + memcpy(addr->sun_path, path, len); + return offsetof(typeof(*addr), sun_path) + len + 1; +} diff --git a/xf86-input-mfndev/src/qubes.c b/xf86-input-mfndev/src/qubes.c index 81eaecf0..82d711ff 100644 --- a/xf86-input-mfndev/src/qubes.c +++ b/xf86-input-mfndev/src/qubes.c @@ -92,6 +92,7 @@ #include "xdriver-shm-cmd.h" #include "qubes.h" #include "labels.h" +#include "unix-addr.h" #include "../../xf86-qubes-common/include/xf86-qubes-common.h" @@ -388,19 +389,22 @@ static int _qubes_init_axes(DeviceIntPtr device) int connect_unix_socket(QubesDevicePtr pQubes); int connect_unix_socket(QubesDevicePtr pQubes) { - int s, len; + int s; struct sockaddr_un remote; + socklen_t len; + + len = sockaddr_un_from_path(&remote, pQubes->device); + if (len == 0) { + xf86Msg(X_ERROR, "invalid socket path: %s\n", pQubes->device); + return -1; + } if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { xf86Msg(X_ERROR, "socket(%s): %s\n", pQubes->device, strerror(errno)); return -1; } - - remote.sun_family = AF_UNIX; - strncpy(remote.sun_path, pQubes->device, sizeof(remote.sun_path)); - len = strlen(remote.sun_path) + sizeof(remote.sun_family); - if (connect(s, (struct sockaddr *) &remote, len) == -1) { + if (connect(s, (struct sockaddr *)&remote, len) == -1) { xf86Msg(X_ERROR, "connect(%s): %s\n", pQubes->device, strerror(errno)); close(s); return -1; @@ -600,12 +604,41 @@ static void dump_window_grant_refs(int window_id, int fd) // the window is destroyed before the driver sees the req goto send_response; + if (x_window->drawable.class == InputOnly) { + // Composite redirect doesn't handle InputOnly windows. There should be + // nothing to display anyway. + xf86Msg(X_ERROR, "can't dump InputOnly windows 0x%x\n", window_id); + goto send_response; + } + + if (!x_window->realized) { + // Composite redirect is setup/teared-down during Realize-/Unrealize- + // Window (called when a window gets mapped/unmapped). So if the window + // is not realized we don't have a per window pixmap we can send. + // + // Note that the map notification is sent before calling RealizeWindow. + // Therefore our video driver sends the agent a custom event when the + // window has actually been realized. + // + // This means that the configure of a window before realization will + // not send grants. This should be fine since we will send grants when + // it's realized and it needs to redraw on expose anyway. + // + // We can get here in a race when a window is being unrealized and/or + // destroyed, as described above for the window lookup. So we might + // have to improve or silence this error message in the furture. + xf86Msg(X_ERROR, "can't dump not realized window 0x%x\n", window_id); + goto send_response; + } + screen = x_window->drawable.pScreen; pixmap = (*screen->GetWindowPixmap) (x_window); priv = xf86_qubes_pixmap_get_private(pixmap); if (priv == NULL) { - xf86Msg(X_ERROR, "can't dump window without grant table allocation\n"); + xf86Msg(X_ERROR, + "can't dump window 0x%x without grant table allocation\n", + window_id); goto send_response; } diff --git a/xf86-video-dummy/src/dummy_driver.c b/xf86-video-dummy/src/dummy_driver.c index 5af1c6fe..5c0a5728 100644 --- a/xf86-video-dummy/src/dummy_driver.c +++ b/xf86-video-dummy/src/dummy_driver.c @@ -56,6 +56,7 @@ #include #include #include "../../include/list.h" +#include "../../include/qubes-video-ext.h" /* Mandatory functions */ static const OptionInfoRec *DUMMYAvailableOptions(int chipid, int busid); @@ -106,6 +107,11 @@ static Bool dummyDriverFunc(ScrnInfoPtr pScrn, xorgDriverFuncOp op, static ScrnInfoPtr DUMMYScrn; /* static-globalize it */ +static ClientPtr agentClient; + +static ExtensionEntry *QVE; + + /* * This is intentionally screen-independent. It indicates the binding * choice made in the first PreInit. @@ -137,8 +143,8 @@ _X_EXPORT DriverRec DUMMY = { }; static SymTabRec DUMMYChipsets[] = { - { DUMMY_CHIP, "dummy" }, - { -1, NULL } + { DUMMY_CHIP, "dummy" }, + { -1, NULL } }; typedef enum { @@ -148,10 +154,10 @@ typedef enum { } DUMMYOpts; static const OptionInfoRec DUMMYOptions[] = { - { OPTION_SW_CURSOR, "SWcursor", OPTV_BOOLEAN, {0}, FALSE }, - { OPTION_RENDER, "Render", OPTV_STRING, {0}, FALSE }, + { OPTION_SW_CURSOR, "SWcursor", OPTV_BOOLEAN, {0}, FALSE }, + { OPTION_RENDER, "Render", OPTV_STRING, {0}, FALSE }, { OPTION_GUI_DOMID, "GUIDomID", OPTV_INTEGER, {0}, FALSE }, - { -1, NULL, OPTV_NONE, {0}, FALSE } + { -1, NULL, OPTV_NONE, {0}, FALSE } }; #ifdef XFree86LOADER @@ -471,6 +477,43 @@ Bool DUMMYAdjustScreenPixmap(ScrnInfoPtr pScrn, int width, int height) */ _X_EXPORT XF86ModuleData dummyqbsModuleData = { &dummyVersRec, dummySetup, NULL }; +static int +QVEDispatch(ClientPtr client) +{ + REQUEST(xQVEReq); + REQUEST_SIZE_MATCH(xQVEReq); + switch (stuff->QVEReqType) { + case X_QVERegister: + agentClient = client; + return Success; + case X_QVEUnregister: + if (agentClient == client) { + agentClient = NULL; + } + return Success; + default: + return BadRequest; + } +} + +static int +QVEDispatchSwapped(ClientPtr client) +{ + xf86Msg(X_ERROR, QVE_NAME " doesn't support swapped clients\n"); + return BadRequest; +} + +static void +QubesClientStateCallback(CallbackListPtr *cbl, void *cb_private, void *data) +{ + NewClientInfoRec *clientinfo = data; + ClientPtr client = clientinfo->client; + + if (client == agentClient && client->clientState == ClientStateGone) { + agentClient = NULL; + } +} + static pointer dummySetup(pointer module, pointer opts, int *errmaj, int *errmin) { @@ -485,6 +528,30 @@ dummySetup(pointer module, pointer opts, int *errmaj, int *errmin) * by calling LoadSubModule(). */ + if (!AddCallback(&ClientStateCallback, QubesClientStateCallback, NULL)) { + xf86Msg(X_ERROR, "Failed to register ClientStateCallback\n"); + if (errmaj) { + *errmaj = LDR_MODSPECIFIC; + } + return NULL; + } + + QVE = AddExtension(QVE_NAME, + QVENumberEvents, + 0 /* number of errors */, + QVEDispatch, + QVEDispatchSwapped, + NULL /* CloseDownProc */, + StandardMinorOpcode); + if (QVE == NULL) { + xf86Msg(X_ERROR, "Failed to register " QVE_NAME " extentions\n"); + if (errmaj) { + *errmaj = LDR_MODSPECIFIC; + } + return NULL; + } + EventSwapVector[QVE->eventBase + QVEWindowRealized] = NotImplemented; + /* * The return value must be non-NULL on success even though there * is no TearDownProc. @@ -568,7 +635,7 @@ DUMMYProbe(DriverPtr drv, int flags) for (i = 0; i < numUsed; i++) { ScrnInfoPtr pScrn = NULL; - int entityIndex = + int entityIndex = xf86ClaimNoSlot(drv,DUMMY_CHIP,devSections[i],TRUE); /* Allocate a ScrnInfoRec and claim the slot */ if ((pScrn = xf86AllocateScreen(drv,0 ))) { @@ -589,7 +656,7 @@ DUMMYProbe(DriverPtr drv, int flags) foundScreen = TRUE; } } - } + } return foundScreen; } @@ -609,21 +676,21 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags) GDevPtr device = xf86GetEntityInfo(pScrn->entityList[0])->device; const char *render, *defaultRender = "/dev/dri/renderD128"; - if (flags & PROBE_DETECT) + if (flags & PROBE_DETECT) return TRUE; - + /* Allocate the DummyRec driverPrivate */ if (!DUMMYGetRec(pScrn)) { return FALSE; } - + dPtr = DUMMYPTR(pScrn); pScrn->chipset = (char *)xf86TokenToString(DUMMYChipsets, DUMMY_CHIP); xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Chipset is a DUMMY\n"); - + pScrn->monitor = pScrn->confScreen->monitor; if (!xf86SetDepthBpp(pScrn, 0, 0, 0, Support24bppFb | Support32bppFb)) @@ -668,7 +735,7 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags) } } - if (!xf86SetDefaultVisual(pScrn, -1)) + if (!xf86SetDefaultVisual(pScrn, -1)) return FALSE; if (pScrn->depth > 1) { @@ -718,8 +785,8 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags) clockRanges->ClockMulFactor = 1; clockRanges->minClock = 11000; /* guessed ยงยงยง */ clockRanges->maxClock = maxClock; - clockRanges->clockIndex = -1; /* programmable */ - clockRanges->interlaceAllowed = TRUE; + clockRanges->clockIndex = -1; /* programmable */ + clockRanges->interlaceAllowed = TRUE; clockRanges->doubleScanAllowed = TRUE; /* Subtract memory for HW cursor */ @@ -755,8 +822,8 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags) * driver and if the driver doesn't provide code to set them. They * are not pre-initialised at all. */ - xf86SetCrtcForModes(pScrn, 0); - + xf86SetCrtcForModes(pScrn, 0); + /* Set the current mode to the first in the list */ pScrn->currentMode = pScrn->modes; @@ -774,7 +841,7 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags) if (!xf86LoadSubModule(pScrn, "ramdac")) RETURN; } - + /* We have no contiguous physical fb in physical memory */ pScrn->memPhysBase = 0; pScrn->fbOffset = 0; @@ -784,7 +851,7 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags) if (!render) render = defaultRender; - + dPtr->fd = open(render, O_RDWR); if (dPtr->fd < 0) xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Open render %s fail\n", render); @@ -809,7 +876,7 @@ static Bool DUMMYEnterVT(VT_FUNC_ARGS_DECL) { SCRN_INFO_PTR(arg); - + /* Should we re-save the text mode on each VT enter? */ if(!dummyModeInit(pScrn, pScrn->currentMode)) return FALSE; @@ -843,7 +910,7 @@ DUMMYLoadPalette( shift = Gshift = 1; break; case 16: - shift = 0; + shift = 0; Gshift = 0; break; default: @@ -856,7 +923,7 @@ DUMMYLoadPalette( dPtr->colors[index].red = colors[index].red << shift; dPtr->colors[index].green = colors[index].green << Gshift; dPtr->colors[index].blue = colors[index].blue << shift; - } + } } @@ -1024,6 +1091,34 @@ qubes_destroy_pixmap(PixmapPtr pixmap) { return fbDestroyPixmap(pixmap); } +static void +sendRealizedNotify(WindowPtr win, Bool unrealized) { + if (agentClient) { + xQVEWindowRealizedEvent e = {}; + e.type = QVE->eventBase + QVEWindowRealized; + + if (unrealized) { + e.detail |= QVEWindowRealizedDetailUnrealized; + } + + e.window = win->drawable.id; + + WriteEventsToClient(agentClient, 1, (xEvent *)&e); + } +} + +static Bool +qubesRealizeWindow(WindowPtr win) { + sendRealizedNotify(win, FALSE); + return TRUE; +} + +static Bool +qubesUnrealizeWindow(WindowPtr win) { + sendRealizedNotify(win, TRUE); + return TRUE; +} + /* Mandatory */ static Bool DUMMYScreenInit(SCREEN_INIT_ARGS_DECL) @@ -1061,12 +1156,12 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL) return FALSE; } - + /* * next we save the current state and setup the first mode */ dummySave(pScrn); - + if (!dummyModeInit(pScrn,pScrn->currentMode)) return FALSE; DUMMYAdjustFrame(ADJUST_FRAME_ARGS(pScrn, pScrn->frameX0, pScrn->frameY0)); @@ -1075,9 +1170,9 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL) * Reset visual list. */ miClearVisualTypes(); - + /* Setup the visuals we support. */ - + if (!miSetVisualTypes(pScrn->depth, miGetDefaultVisualMask(pScrn->depth), pScrn->rgbBits, pScrn->defaultVisual)) @@ -1156,6 +1251,8 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL) pScreen->CreatePixmap = qubes_create_pixmap; pScreen->DestroyPixmap = qubes_destroy_pixmap; + pScreen->RealizeWindow = qubesRealizeWindow; + pScreen->UnrealizeWindow = qubesUnrealizeWindow; PictureScreenPtr ps = GetPictureScreenIfSet(pScreen); ps->Glyphs = fbGlyphs; dPtr->CreateScreenResources = pScreen->CreateScreenResources; @@ -1211,7 +1308,7 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL) } /* XRANDR initialization end */ - + if (dPtr->swCursor) xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, "Using Software Cursor.\n"); @@ -1225,9 +1322,9 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL) AvailFBArea.y1 = 0; AvailFBArea.x2 = pScrn->displayWidth; AvailFBArea.y2 = lines; - xf86InitFBManager(pScreen, &AvailFBArea); + xf86InitFBManager(pScreen, &AvailFBArea); - xf86DrvMsg(pScrn->scrnIndex, X_INFO, + xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Using %i scanlines of offscreen memory \n" , lines - pScrn->virtualY); } @@ -1253,8 +1350,8 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL) return FALSE; if (!xf86HandleColormaps(pScreen, 256, pScrn->rgbBits, - DUMMYLoadPalette, NULL, - CMAP_PALETTED_TRUECOLOR + DUMMYLoadPalette, NULL, + CMAP_PALETTED_TRUECOLOR | CMAP_RELOAD_ON_MODE_SWITCH)) return FALSE; @@ -1292,7 +1389,7 @@ DUMMYSwitchMode(SWITCH_MODE_ARGS_DECL) DUMMYAdjustFrame(ADJUST_FRAME_ARGS_DECL) { SCRN_INFO_PTR(arg); - int Base; + int Base; Base = (y * pScrn->displayWidth + x) >> 2; @@ -1363,7 +1460,7 @@ DUMMYSaveScreen(ScreenPtr pScreen, int mode) dPtr = DUMMYPTR(pScrn); dPtr->screenSaver = xf86IsUnblank(mode); - } + } return TRUE; } @@ -1379,7 +1476,7 @@ dummySave(ScrnInfoPtr pScrn) { } - static void + static void dummyRestore(ScrnInfoPtr pScrn, Bool restoreText) { } @@ -1421,7 +1518,7 @@ DUMMYCreateWindow(WindowPtr pWin) VFB_PROP = MakeAtom(VFB_PROP_NAME, strlen(VFB_PROP_NAME), 1); #if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) < 21 - ret = ChangeWindowProperty(pWinRoot, VFB_PROP, XA_STRING, + ret = ChangeWindowProperty(pWinRoot, VFB_PROP, XA_STRING, 8, PropModeReplace, (int)4, (pointer)"TRUE", FALSE); #else ret = dixChangeWindowProperty(serverClient, pWinRoot,