fix(kiosk): hide cursor inside WebViews + at compositor level

WebView showed a grey/black half-square in the top-left — that's GDK's
"none" cursor rendered by WebKit's own surface, which ignores the
GTK-window CSS we set elsewhere. Inject a WebKit UserStyleSheet that
applies cursor:none !important to every page + frame at User priority,
overriding page-author CSS.

For the boot gap (cage start → first kiosk frame), set XCURSOR_SIZE=1
and WLR_NO_HARDWARE_CURSORS=1 in the systemd unit. SW fallback honors
the 1-pixel size; HW cursors don't, which is why a default arrow leaks
through on some Pi GPUs.
This commit is contained in:
Mitchell R 2026-05-21 10:21:17 +02:00
parent 7df048c195
commit e26af6c0cc
No known key found for this signature in database
2 changed files with 26 additions and 0 deletions

View file

@ -29,6 +29,12 @@ EnvironmentFile=-/etc/default/betterframe-kiosk
Environment=XDG_SESSION_TYPE=wayland
Environment=XDG_SESSION_CLASS=user
Environment=GST_DEBUG=1
# Cursor: cage/wlroots draws a sprite in the gap between compositor start
# and first kiosk frame. Collapse to 1px transparent + force software
# fallback so XCURSOR_SIZE actually applies (HW cursors ignore size on
# some GPUs, leaving a default white-arrow visible in the corner).
Environment=XCURSOR_SIZE=1
Environment=WLR_NO_HARDWARE_CURSORS=1
# Let the unprivileged kiosk process control the Pi fan PWM sysfs files.
ExecStartPre=+/bin/sh -c 'for d in /sys/class/hwmon/hwmon*; do [ -e "$d/pwm1" ] || continue; chgrp bfkiosk "$d/pwm1" "$d/pwm1_enable" 2>/dev/null || true; chmod g+w "$d/pwm1" "$d/pwm1_enable" 2>/dev/null || true; done'
ExecStartPre=+/usr/local/sbin/betterframe-firmware-rollback.sh

View file

@ -1561,6 +1561,26 @@ fn ensure_web(
let wv = webkit6::WebView::new();
wv.set_vexpand(true);
wv.set_hexpand(true);
// Hide the pointer inside every WebKit page. The default GTK CSS cursor:
// none we set on top-level windows doesn't propagate into the WebView's
// own surface — it draws its own cursor over hovered HTML elements.
// Inject a UserStyleSheet at the WebKit level so every page (and every
// frame) hides the cursor unconditionally. UserStyleLevel::User wins
// over page-author CSS.
{
use webkit6::prelude::*;
let ucm = wv.user_content_manager();
let style = webkit6::UserStyleSheet::new(
"*, *::before, *::after { cursor: none !important; }",
webkit6::UserContentInjectedFrames::AllFrames,
webkit6::UserStyleLevel::User,
&[],
&[],
);
ucm.add_style_sheet(&style);
}
match source {
WebSource::Html(html) => {
webkit6::prelude::WebViewExt::load_html(&wv, html, None);