Show a real cursor in standalone mode (wlr_cursor, software-composited)
Standalone has no host compositor to draw a pointer, so give the output a real wlr_cursor driven by XCURSOR_THEME/XCURSOR_SIZE. Force software cursors on every standalone output (the headless backend's set_cursor hook would trick wlroots into skipping the scene pass while drawing nothing, and a hardware plane would float the cursor outside the composited buffer). Refuse to run when no 'default' sprite resolves: wlroots 0.19 substitutes a builtin cursor silently, so check the theme dirs ourselves along the loader's XCURSOR_PATH search and hard-fail instead of presenting an invisible pointer. Whole-output damage on every pointer move/warp, because the scene renderer only composites the software cursor inside the damaged region and raw cursor motion damages only the output. The stdin feed gains CTL_CMD_WARP so tests can place the pointer; the visible-cursor test probes through the GL dump's bottom-up flip, and a missing-theme regression test asserts rc 1.
This commit is contained in:
parent
c4a4b73393
commit
9db00591ca
2 changed files with 254 additions and 0 deletions
155
tabbed-wlroots.c
155
tabbed-wlroots.c
|
|
@ -66,17 +66,20 @@
|
|||
#include <wlr/render/wlr_texture.h>
|
||||
#include <wlr/types/wlr_buffer.h>
|
||||
#include <wlr/types/wlr_compositor.h>
|
||||
#include <wlr/types/wlr_cursor.h>
|
||||
#include <wlr/types/wlr_data_control_v1.h>
|
||||
#include <wlr/types/wlr_data_device.h>
|
||||
#include <wlr/types/wlr_input_device.h>
|
||||
#include <wlr/types/wlr_keyboard.h>
|
||||
#include <wlr/types/wlr_linux_dmabuf_v1.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include <wlr/types/wlr_output_layout.h>
|
||||
#include <wlr/types/wlr_pointer.h>
|
||||
#include <wlr/types/wlr_scene.h>
|
||||
#include <wlr/types/wlr_seat.h>
|
||||
#include <wlr/types/wlr_shm.h>
|
||||
#include <wlr/types/wlr_subcompositor.h>
|
||||
#include <wlr/types/wlr_xcursor_manager.h>
|
||||
#include <wlr/types/wlr_xdg_shell.h>
|
||||
#include <wlr/util/log.h>
|
||||
|
||||
|
|
@ -126,6 +129,11 @@ struct tabbed_server {
|
|||
/* Transparent full-output sentinel used to force whole-output damage on
|
||||
overlay transitions; see standalone_overlay_damage(). */
|
||||
struct wlr_scene_rect *overlay_damage_rect;
|
||||
/* Standalone-only cursor: a real wlr_cursor so the pointer is visible
|
||||
(hardware cursor plane on DRM, scene-composited on headless). */
|
||||
struct wlr_cursor *cursor;
|
||||
struct wlr_xcursor_manager *xcursor_mgr;
|
||||
struct wlr_output_layout *output_layout;
|
||||
struct wlr_seat *seat;
|
||||
struct wlr_xdg_shell *xdg_shell;
|
||||
struct wlr_keyboard keyboard;
|
||||
|
|
@ -1627,6 +1635,20 @@ server.output = output;
|
|||
server.scene_output = wlr_scene_output_create(server.scene, output);
|
||||
wlr_scene_output_set_position(server.scene_output, 0, 0);
|
||||
|
||||
/* Always render the pointer into our own frame buffer (software cursor)
|
||||
instead of a hardware cursor plane: the headless backend's set_cursor
|
||||
hook makes wlroots believe it has a plane (so the scene software pass
|
||||
is skipped) while actually drawing nothing, and a hardware plane would
|
||||
float the cursor outside the buffer we composite/dump. */
|
||||
if (server.standalone)
|
||||
wlr_output_lock_software_cursors(output, true);
|
||||
|
||||
/* Give the standalone cursor a place to live. Idempotent across VT
|
||||
round-trips (wlr_output_layout_add reuses the existing entry), and the
|
||||
cursor attaches its output cursor via the layout's add signal. */
|
||||
if (server.cursor)
|
||||
wlr_output_layout_add(server.output_layout, output, 0, 0);
|
||||
|
||||
/* Render whenever the output is ready for a frame (refresh-rate driven),
|
||||
like tinywl; this is what drives client frame callbacks / surface
|
||||
enter events even before any damage has accumulated. */
|
||||
|
|
@ -3250,6 +3272,9 @@ static void dump_standalone_buffer(struct wlr_buffer *buf) {
|
|||
fclose(f);
|
||||
tabbed_log(TABBED_LOG_INFO, "[dump] host back buffer -> %s (%dx%d)\n",
|
||||
path, w, h);
|
||||
if (server.cursor)
|
||||
tabbed_log(TABBED_LOG_INFO,
|
||||
"[dump] cursor at (%f,%f)\n", server.cursor->x, server.cursor->y);
|
||||
}
|
||||
free(px);
|
||||
dump_seq++;
|
||||
|
|
@ -3325,6 +3350,21 @@ static void update_overlay_visible(bool alt) {
|
|||
needs_redraw = true;
|
||||
}
|
||||
|
||||
/* handle_new_output() forces software cursors on every standalone output, so
|
||||
the pointer is always composited into our frame by the scene renderer --
|
||||
which only repaints where the frame was damaged. Cursor motion damages the
|
||||
*output*, not the scene, so force whole-output damage on every move or the
|
||||
pointer would never be painted. */
|
||||
static void standalone_cursor_motion(struct wl_listener *listener, void *data) {
|
||||
(void)listener; (void)data;
|
||||
if (server.standalone)
|
||||
standalone_overlay_damage();
|
||||
}
|
||||
|
||||
static struct wl_listener standalone_cursor_motion_listener = {
|
||||
.notify = standalone_cursor_motion,
|
||||
};
|
||||
|
||||
/* Shared key handling for both the host keyboard and native input devices:
|
||||
tab-switch bindings + overlay visibility, with the caller forwarding the
|
||||
key to the seat afterwards. `mods_depressed` uses the wl_keyboard modifier
|
||||
|
|
@ -3618,6 +3658,8 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
|
|||
}
|
||||
case WLR_INPUT_DEVICE_POINTER: {
|
||||
struct wlr_pointer *ptr = wlr_pointer_from_input_device(device);
|
||||
if (server.cursor)
|
||||
wlr_cursor_attach_input_device(server.cursor, device);
|
||||
struct standalone_ptrdev *pd = calloc(1, sizeof(*pd));
|
||||
if (!pd) break;
|
||||
pd->pointer = ptr;
|
||||
|
|
@ -3652,6 +3694,7 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
|
|||
*/
|
||||
#define CTL_CMD_KEY 1
|
||||
#define CTL_CMD_MODS 2
|
||||
#define CTL_CMD_WARP 3
|
||||
|
||||
#define CTL_MAX_MSG 20 /* 1 + 4 + 4 (or 1 + 16) after padding */
|
||||
#define CTL_BUF_SIZE 512
|
||||
|
|
@ -3676,6 +3719,17 @@ static void ctl_dispatch(const uint8_t *msg, size_t len) {
|
|||
host_kbd_modifiers(NULL, NULL, ++ctl_serial, u32[0], u32[1],
|
||||
u32[2], u32[3]);
|
||||
break;
|
||||
case CTL_CMD_WARP:
|
||||
if (len < 9) return;
|
||||
memcpy(u32, msg + 1, 8);
|
||||
ctl_time += 1;
|
||||
/* Absolute standalone-output-pixel position, so tests can place the
|
||||
visible cursor (wlr_cursor_warp with a NULL device). Warp doesn't
|
||||
emit a motion event, so damage the scene here like motion would. */
|
||||
wlr_cursor_warp(server.cursor, NULL, (double)u32[0], (double)u32[1]);
|
||||
if (server.standalone)
|
||||
standalone_overlay_damage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4435,6 +4489,54 @@ static bool backends_include(const char *env, const char *name) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/* True if `theme` resolves to a real cursors file on disk. Mirrors the
|
||||
loader's search: XCURSOR_PATH verbatim, else $XDG_DATA_HOME/icons (or
|
||||
~/.local/share/icons) plus the compiled-in paths, each expected to contain
|
||||
<dir>/<theme>/cursors/. wlroots 0.19 silently substitutes a builtin cursor
|
||||
when none is found, so a missing theme must be detected here for the
|
||||
hard-fail path. */
|
||||
static bool standalone_cursor_theme_resolvable(const char *theme) {
|
||||
const char *env = getenv("XCURSOR_PATH");
|
||||
char *path = NULL;
|
||||
if (env && *env) {
|
||||
path = strdup(env);
|
||||
} else {
|
||||
const char *xdg = getenv("XDG_DATA_HOME");
|
||||
const char *udata = (xdg && xdg[0] == '/') ? xdg : "~/.local/share";
|
||||
size_t n = strlen(udata) + strlen(
|
||||
"/icons:/usr/share/icons:/usr/share/pixmaps:"
|
||||
"~/.cursors:/usr/share/cursors/xorg-x11:"
|
||||
"/usr/X11R6/lib/X11/icons") + 1;
|
||||
path = malloc(n);
|
||||
if (path)
|
||||
snprintf(path, n, "%s%s", udata,
|
||||
"/icons:/usr/share/icons:/usr/share/pixmaps:"
|
||||
"~/.cursors:/usr/share/cursors/xorg-x11:"
|
||||
"/usr/X11R6/lib/X11/icons");
|
||||
}
|
||||
if (!path)
|
||||
return false;
|
||||
|
||||
bool found = false;
|
||||
char candid[1024];
|
||||
for (char *dir = strtok(path, ":"); dir; dir = strtok(NULL, ":")) {
|
||||
if (dir[0] == '~') {
|
||||
const char *home = getenv("HOME");
|
||||
if (!home) continue;
|
||||
snprintf(candid, sizeof(candid), "%s%s", home, dir + 1);
|
||||
dir = candid;
|
||||
}
|
||||
size_t n = snprintf(candid, sizeof(candid), "%s/%s/cursors/default",
|
||||
dir, theme);
|
||||
if (n < sizeof(candid) && access(candid, F_OK) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(path);
|
||||
return found;
|
||||
}
|
||||
|
||||
/* ===================== Main ===================== */
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
|
@ -4625,6 +4727,59 @@ int main(int argc, char *argv[]) {
|
|||
if (!server.standalone)
|
||||
bridge_attach_seat();
|
||||
|
||||
/* Standalone has no host compositor to draw a pointer, so give the
|
||||
output a real wlr_cursor. Theme/size follow the XDG cursor convention:
|
||||
XCURSOR_THEME (default "default") and XCURSOR_SIZE (default 24px).
|
||||
wlroots itself never derives these, so read them here -- and refuse to
|
||||
run if the resolved theme has no usable cursor, rather than silently
|
||||
presenting an invisible pointer. */
|
||||
if (server.standalone) {
|
||||
server.output_layout = wlr_output_layout_create(server.wl_display);
|
||||
server.cursor = wlr_cursor_create();
|
||||
if (!server.output_layout || !server.cursor) {
|
||||
fprintf(stderr,
|
||||
"tabbed-wlroots: failed to create output layout/cursor\n");
|
||||
return 1;
|
||||
}
|
||||
wlr_cursor_attach_output_layout(server.cursor, server.output_layout);
|
||||
|
||||
const char *theme_env = getenv("XCURSOR_THEME");
|
||||
const char *theme = theme_env && *theme_env ? theme_env : "default";
|
||||
uint32_t size = 24;
|
||||
const char *size_env = getenv("XCURSOR_SIZE");
|
||||
if (size_env) {
|
||||
char *end = NULL;
|
||||
unsigned long v = strtoul(size_env, &end, 10);
|
||||
if (end != size_env && v > 0)
|
||||
size = (uint32_t)v;
|
||||
}
|
||||
/* wlroots 0.19 silently substitutes a builtin cursor when the theme
|
||||
directory holds nothing, so a missing theme can't be detected from
|
||||
the manager afterwards. Check the theme dirs ourselves, following
|
||||
the same search path the loader uses (XCURSOR_PATH, else the
|
||||
compiled default), and require a real 'default' sprite. */
|
||||
if (!standalone_cursor_theme_resolvable(theme)) {
|
||||
fprintf(stderr,
|
||||
"tabbed-wlroots: no cursor theme '%s' is installed (need a "
|
||||
"'default' sprite in <icon-dir>/%s/cursors). Install a cursor "
|
||||
"theme or set XCURSOR_THEME/XCURSOR_PATH\n", theme, theme);
|
||||
return 1;
|
||||
}
|
||||
server.xcursor_mgr = wlr_xcursor_manager_create(theme, size);
|
||||
if (server.xcursor_mgr)
|
||||
wlr_xcursor_manager_load(server.xcursor_mgr, 1);
|
||||
if (!server.xcursor_mgr ||
|
||||
!wlr_xcursor_manager_get_xcursor(
|
||||
server.xcursor_mgr, "default", 1)) {
|
||||
fprintf(stderr,
|
||||
"tabbed-wlroots: failed to load cursor theme '%s'\n", theme);
|
||||
return 1;
|
||||
}
|
||||
wlr_cursor_set_xcursor(server.cursor, server.xcursor_mgr, "default");
|
||||
wl_signal_add(&server.cursor->events.motion,
|
||||
&standalone_cursor_motion_listener);
|
||||
}
|
||||
|
||||
/* virtual keyboard fed from the host keyboard */
|
||||
static const struct wlr_keyboard_impl keyboard_impl = {
|
||||
.name = "tabbed-keyboard",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue