Skip to content

Commit 5f2eae2

Browse files
committed
When handling a cursor change event with an empty cursor_name (Chromium), try matching on the cursor's bitmap image before falling back to the default cursor
1 parent 896edfd commit 5f2eae2

2 files changed

Lines changed: 79 additions & 3 deletions

File tree

gui-agent/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ CFLAGS += -I../include/ `pkg-config --cflags vchan` \
2525
-Wmissing-prototypes -Wstrict-prototypes -Wold-style-declaration \
2626
-Wold-style-definition
2727
OBJS = vmside.o txrx-vchan.o error.o list.o encoding.o
28-
LIBS = -lX11 -lXdamage -lXcomposite -lXfixes `pkg-config --libs vchan` -lqubesdb \
28+
LIBS = -lX11 -lXdamage -lXcomposite -lXcursor -lXfixes `pkg-config --libs vchan` -lqubesdb \
2929
-lunistring
3030

3131

gui-agent/vmside.c

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include <X11/XKBlib.h>
4545
#include <X11/Xatom.h>
4646
#include <X11/cursorfont.h>
47+
#include <X11/Xcursor/Xcursor.h>
4748
#include <qubes-gui-protocol.h>
4849
#include <qubes-xorg-tray-defs.h>
4950
#include "xdriver-shm-cmd.h"
@@ -420,6 +421,76 @@ static uint32_t find_cursor(Ghandles *g, Atom atom)
420421
return CURSOR_DEFAULT;
421422
}
422423

424+
/*
425+
* Some applications don't set cursor names properly when sending XfixesDisplayCursorNotify events, notably Chromium and derivatives.
426+
* Before falling back to CURSOR_DEFAULT, we'll try to match (quick hashes of) the live cursor's bitmap with each supported cursor's bitmap.
427+
* TODO: Precompute the hashes of supported_cursors once during init to avoid redundant computations everytime a cursor changes (this might not be trivial).
428+
**/
429+
430+
// Generic Fowler-Noll-Vo quick hash function (FNV-1a), magic mix number from WikiPedia's entry
431+
static uint64_t fnv1a64(const void *data, size_t len, uint64_t seed) {
432+
const uint8_t *p = data;
433+
uint64_t hash = seed;
434+
for (size_t i = 0; i < len; i++) {
435+
hash ^= p[i];
436+
hash *= 1099511628211ULL;
437+
}
438+
439+
return hash;
440+
}
441+
442+
// Specialized FNV-1a hash of a cursor, magic seed number from WikiPedia's entry
443+
static uint64_t hash_cursor(uint32_t w, uint32_t h,
444+
uint32_t xhot, uint32_t yhot,
445+
const uint32_t *pixels) {
446+
uint64_t hash = 14695981039346656037ULL;
447+
448+
uint32_t hdr[4] = { w, h, xhot, yhot };
449+
hash = fnv1a64(hdr, sizeof(hdr), hash);
450+
hash = fnv1a64(pixels, (size_t)(w * h * sizeof(uint32_t)), hash);
451+
452+
return hash;
453+
}
454+
455+
// Fallback function to lookup an unnamed cursor by its bitmap
456+
static uint32_t find_cursor_by_image(Ghandles *g) {
457+
XFixesCursorImage *img = XFixesGetCursorImage(g->display);
458+
if (!img) return CURSOR_DEFAULT;
459+
460+
/* Narrow unsigned long pixels to uint32_t */
461+
size_t npx = (size_t)img->width * img->height;
462+
uint32_t *live_px = malloc(npx * sizeof(uint32_t));
463+
if (!live_px) {
464+
XFree(img);
465+
return CURSOR_DEFAULT;
466+
}
467+
for (size_t i = 0; i < npx; i++) live_px[i] = (uint32_t)img->pixels[i];
468+
469+
uint64_t live_hash = hash_cursor(img->width, img->height, img->xhot, img->yhot, live_px);
470+
free(live_px);
471+
472+
/* Use the live cursor's own size to avoid potential discrepancies between root's and the user's themes */
473+
uint32_t size = (img->width > img->height) ? img->width : img->height;
474+
XFree(img);
475+
476+
char *theme = XcursorGetTheme(g->display);
477+
for (size_t i = 0; i < NUM_SUPPORTED_CURSORS; i++) {
478+
XcursorImage *img = XcursorLibraryLoadImage(supported_cursors[i].name, theme, size);
479+
if (!img) continue;
480+
481+
uint64_t hash = hash_cursor(img->width, img->height, img->xhot, img->yhot, img->pixels);
482+
XcursorImageDestroy(img);
483+
484+
if (hash == live_hash) {
485+
uint32_t found = CURSOR_X11 + supported_cursors[i].cursor_id;
486+
assert(found < CURSOR_X11_MAX);
487+
return found;
488+
}
489+
}
490+
491+
return CURSOR_DEFAULT;
492+
}
493+
423494
static void process_xevent_cursor(Ghandles *g, XFixesCursorNotifyEvent *ev)
424495
{
425496
if (ev->subtype == XFixesDisplayCursorNotify) {
@@ -430,7 +501,6 @@ static void process_xevent_cursor(Ghandles *g, XFixesCursorNotifyEvent *ev)
430501
int root_x, root_y, win_x, win_y;
431502
unsigned int mask;
432503
Bool ret;
433-
int cursor;
434504

435505
ret = XQueryPointer(g->display, ev->window, &root,
436506
&window_under_pointer,
@@ -441,7 +511,13 @@ static void process_xevent_cursor(Ghandles *g, XFixesCursorNotifyEvent *ev)
441511
if (!lookup_window(g, windows_list, window_under_pointer, __func__))
442512
return;
443513

444-
cursor = find_cursor(g, ev->cursor_name);
514+
uint32_t cursor;
515+
if (ev->cursor_name != None) {
516+
cursor = find_cursor(g, ev->cursor_name);
517+
} else {
518+
cursor = find_cursor_by_image(g);
519+
}
520+
445521
send_cursor(g, window_under_pointer, cursor);
446522
}
447523
}

0 commit comments

Comments
 (0)