fix: composite overlay thumbnails from live textures for subsurface windows
Firefox paints into subsurfaces while its main surface stays transparent, so the single-buffer snapshot saw only black. Windows with subsurface content now composite at overlay-draw time from each live surface texture (which outlives current.buffer), while plain windows keep the legacy snapshot path. Adds wltest --subsurface repro mode and a regression test.
This commit is contained in:
parent
8b8137684a
commit
38a17401d1
3 changed files with 406 additions and 19 deletions
272
kick.c
272
kick.c
|
|
@ -194,6 +194,18 @@ struct kick_tab {
|
|||
struct wl_list windows; /* kick_window.link */
|
||||
};
|
||||
|
||||
/* One surface in a window's subsurface tree watched for thumbnail commits.
|
||||
Lives in w->thumb_subs from thumb_sub_wire until the surface dies or the
|
||||
window goes away (thumb_release); either side frees it exactly once. */
|
||||
struct kick_thumb_sub {
|
||||
struct wl_list link; /* w->thumb_subs */
|
||||
struct kick_window *w;
|
||||
struct wlr_surface *surface;
|
||||
struct wl_listener commit;
|
||||
struct wl_listener destroy;
|
||||
struct wl_listener new_subsurface;
|
||||
};
|
||||
|
||||
/* One window. Every surface kick shows -- a command's first toplevel (a
|
||||
black pending tab is its still-unsurfaced window), a grouped second
|
||||
window from the same client, an external client's window -- is one
|
||||
|
|
@ -317,11 +329,13 @@ struct kick_window {
|
|||
struct wlr_xdg_toplevel_icon_v1 *proto_icon;
|
||||
bool icon_protocol;
|
||||
/* Live overlay-preview thumbnail (--overlay-previews): the window's
|
||||
latest frame, routed per commit to whichever source is fresh --
|
||||
a CPU snapshot (fresh SHM attaches), a locked dma-buf (GPU
|
||||
attaches), or the surface's own texture (in-place SHM updates,
|
||||
where current.buffer is already NULL at commit time). Released
|
||||
with the window. */
|
||||
latest frame. Windows without subsurfaces route each commit to
|
||||
whichever source is fresh -- a CPU snapshot (fresh SHM attaches),
|
||||
a locked dma-buf (GPU attaches), or the surface's own texture
|
||||
(in-place SHM updates, where current.buffer is already NULL at
|
||||
commit time). Windows with subsurface content (e.g. Firefox)
|
||||
composite at overlay-draw time from live surface textures instead,
|
||||
which outlive current.buffer. Released with the window. */
|
||||
struct wlr_texture *thumb_tex;
|
||||
struct wlr_buffer *thumb_buf;
|
||||
uint32_t *thumb_px;
|
||||
|
|
@ -329,6 +343,13 @@ struct kick_window {
|
|||
bool thumb_dirty;
|
||||
enum { THUMB_SRC_NONE, THUMB_SRC_SNAP, THUMB_SRC_DMABUF,
|
||||
THUMB_SRC_LIVE } thumb_src;
|
||||
/* Subsurface commit tracking for overlay thumbnails: one entry per
|
||||
surface in the window's subsurface tree (main surface first). A
|
||||
subsurface-only paint fires no toplevel commit, so each surface
|
||||
carries its own commit listener re-running thumb_capture, and
|
||||
new_subsurface chains surfaces created after the claim. Emptied by
|
||||
thumb_release with the window. */
|
||||
struct wl_list thumb_subs; /* kick_thumb_sub.link */
|
||||
/* Client-supplied tag + description (xdg-toplevel-tag-v1): e.g. "main
|
||||
window" plus its translation. Shown in the overlay row ("title
|
||||
[tag]") and used for --tab-groups grouping; NULL until the client
|
||||
|
|
@ -1131,6 +1152,7 @@ static void output_mgmt_publish(void);
|
|||
static struct kick_tab *pick_successor(struct kick_tab *tab,
|
||||
bool prefer_right);
|
||||
static void remove_tab(struct kick_tab *tab);
|
||||
static int tab_index(struct kick_tab *tab);
|
||||
static int add_tab(const char *cmd);
|
||||
/* set_title / set_app_id listener: refresh the overlay row and mirror the
|
||||
new title/app_id onto the taskbar handles. */
|
||||
|
|
@ -2682,10 +2704,24 @@ static int overlay_preview_snap_h(void) {
|
|||
commit handler and routes each commit to its fresh source: a fresh attach
|
||||
snapshots (SHM) or locks (dma-buf) the committed buffer; an in-place SHM
|
||||
update (current.buffer already NULL) leaves the pixels in the surface's
|
||||
own texture, which wlroots refreshed from the dropped buffer. Draw
|
||||
serves whichever source the latest commit took. */
|
||||
own texture, which wlroots refreshed from the dropped buffer. Windows
|
||||
whose content lives in subsurfaces (e.g. Firefox) instead composite the
|
||||
whole surface tree at overlay-draw time from live textures, which
|
||||
outlive current.buffer. Draw serves whichever source fits the window. */
|
||||
static void thumb_sub_release(struct kick_thumb_sub *sub) {
|
||||
if (!sub) return;
|
||||
wl_list_remove(&sub->commit.link);
|
||||
wl_list_remove(&sub->destroy.link);
|
||||
wl_list_remove(&sub->new_subsurface.link);
|
||||
wl_list_remove(&sub->link);
|
||||
free(sub);
|
||||
}
|
||||
|
||||
static void thumb_release(struct kick_window *w) {
|
||||
if (!w) return;
|
||||
struct kick_thumb_sub *sub, *tmp;
|
||||
wl_list_for_each_safe(sub, tmp, &w->thumb_subs, link)
|
||||
thumb_sub_release(sub);
|
||||
if (w->thumb_tex) {
|
||||
wlr_texture_destroy(w->thumb_tex);
|
||||
w->thumb_tex = NULL;
|
||||
|
|
@ -2701,10 +2737,10 @@ static void thumb_release(struct kick_window *w) {
|
|||
w->thumb_src = THUMB_SRC_NONE;
|
||||
}
|
||||
|
||||
static void thumb_capture(struct kick_window *w) {
|
||||
if (!w || !opt_overlay_previews) return;
|
||||
struct wlr_surface *s = window_surface(w);
|
||||
struct wlr_buffer *b = (s && s->current.buffer) ? s->current.buffer : NULL;
|
||||
/* Single-surface capture: the legacy fast path for windows without
|
||||
subsurfaces. `s` is the main surface, `b` its current buffer. */
|
||||
static void thumb_capture_single(struct kick_window *w, struct wlr_surface *s,
|
||||
struct wlr_buffer *b) {
|
||||
if (b && b->width > 0 && b->height > 0) {
|
||||
/* Route dma-buf straight to the GPU path: probing it with a CPU
|
||||
data-pointer access first can stall on GPU fences at 60Hz, which
|
||||
|
|
@ -2821,6 +2857,199 @@ static void thumb_capture(struct kick_window *w) {
|
|||
kick_request_redraw();
|
||||
}
|
||||
|
||||
/* Tree-thumbnail collection: two wlr_surface_for_each_surface walks over the
|
||||
window (bottom to top) at overlay-draw time. Positions are root-relative
|
||||
surface coordinates; sizes come from each surface's live texture, so the
|
||||
composite matches what the scene renders -- including subsurface content
|
||||
whose current.buffer is long gone by draw time. Textures persist on the
|
||||
surface from the last commit, so hidden tabs show their last frame. */
|
||||
struct thumb_extent {
|
||||
int minx, miny, maxx, maxy;
|
||||
bool any;
|
||||
};
|
||||
|
||||
struct thumb_draw_ctx {
|
||||
struct wlr_render_pass *pass;
|
||||
float alpha;
|
||||
int ox, oy; /* extent origin in root coordinates */
|
||||
double scale; /* window pixels -> thumb pixels */
|
||||
int dx0, dy0; /* thumb-box origin of the fitted extent */
|
||||
};
|
||||
|
||||
static void thumb_extent_iter(struct wlr_surface *surface, int sx, int sy,
|
||||
void *data) {
|
||||
struct thumb_extent *e = data;
|
||||
struct wlr_texture *tex = surface ? wlr_surface_get_texture(surface) : NULL;
|
||||
if (!tex || tex->width <= 0 || tex->height <= 0) return;
|
||||
if (!e->any) {
|
||||
e->minx = sx;
|
||||
e->miny = sy;
|
||||
e->maxx = sx + tex->width;
|
||||
e->maxy = sy + tex->height;
|
||||
e->any = true;
|
||||
return;
|
||||
}
|
||||
if (sx < e->minx) e->minx = sx;
|
||||
if (sy < e->miny) e->miny = sy;
|
||||
if (sx + tex->width > e->maxx) e->maxx = sx + tex->width;
|
||||
if (sy + tex->height > e->maxy) e->maxy = sy + tex->height;
|
||||
}
|
||||
|
||||
static void thumb_draw_iter(struct wlr_surface *surface, int sx, int sy,
|
||||
void *data) {
|
||||
struct thumb_draw_ctx *c = data;
|
||||
struct wlr_texture *tex = surface ? wlr_surface_get_texture(surface) : NULL;
|
||||
if (!tex || tex->width <= 0 || tex->height <= 0) return;
|
||||
struct wlr_render_texture_options o = {
|
||||
.texture = tex,
|
||||
.src_box = { .x = 0, .y = 0,
|
||||
.width = (float)tex->width, .height = (float)tex->height },
|
||||
.dst_box = { .x = (float)(c->dx0 + (sx - c->ox) * c->scale),
|
||||
.y = (float)(c->dy0 + (sy - c->oy) * c->scale),
|
||||
.width = (float)(tex->width * c->scale),
|
||||
.height = (float)(tex->height * c->scale) },
|
||||
.alpha = &c->alpha,
|
||||
.filter_mode = WLR_SCALE_FILTER_BILINEAR,
|
||||
.blend_mode = WLR_RENDER_BLEND_MODE_PREMULTIPLIED,
|
||||
};
|
||||
wlr_render_pass_add_texture(c->pass, &o);
|
||||
}
|
||||
|
||||
/* Draw-time tree thumbnail: composite every live surface texture at its
|
||||
root-relative offset, aspect-fit into the thumb box like the
|
||||
single-surface path. Textures outlive current.buffer (cleared by commit
|
||||
finalization), so this sees subsurface content no commit hook can. */
|
||||
static void overlay_pass_tree_thumb(struct wlr_render_pass *pass,
|
||||
struct kick_window *w,
|
||||
int tx, int ty, int tw, int th,
|
||||
float alpha) {
|
||||
struct wlr_surface *s = window_surface(w);
|
||||
if (!s) return;
|
||||
struct thumb_extent e = { 0 };
|
||||
wlr_surface_for_each_surface(s, thumb_extent_iter, &e);
|
||||
if (!e.any || e.maxx <= e.minx || e.maxy <= e.miny) return;
|
||||
int ew = e.maxx - e.minx, eh = e.maxy - e.miny;
|
||||
int dw = tw, dh = (int)((int64_t)tw * eh / ew);
|
||||
if (dh > th) {
|
||||
dh = th;
|
||||
dw = (int)((int64_t)th * ew / eh);
|
||||
}
|
||||
if (dw <= 0 || dh <= 0) return;
|
||||
struct thumb_draw_ctx c = {
|
||||
.pass = pass,
|
||||
.alpha = alpha,
|
||||
.ox = e.minx, .oy = e.miny,
|
||||
.scale = (double)dw / (double)ew,
|
||||
.dx0 = tx + (tw - dw) / 2,
|
||||
.dy0 = ty + (th - dh) / 2,
|
||||
};
|
||||
wlr_surface_for_each_surface(s, thumb_draw_iter, &c);
|
||||
}
|
||||
|
||||
/* Whether the window owns subsurface content: such windows composite at
|
||||
draw time from live textures (see overlay_pass_tree_thumb). */
|
||||
static bool window_has_subsurfaces(struct kick_window *w) {
|
||||
struct wlr_surface *s = window_surface(w);
|
||||
if (!s) return false;
|
||||
return !wl_list_empty(&s->current.subsurfaces_above) ||
|
||||
!wl_list_empty(&s->current.subsurfaces_below);
|
||||
}
|
||||
|
||||
/* Tree windows composite at draw time from live textures; capture only
|
||||
repaints while the overlay is held. */
|
||||
static void thumb_capture_tree(struct kick_window *w) {
|
||||
(void)w;
|
||||
if (host.overlay_visible)
|
||||
kick_request_redraw();
|
||||
}
|
||||
|
||||
/* Route a commit to its thumbnail source. Windows without subsurfaces
|
||||
snapshot the committed buffer synchronously (current.buffer is only
|
||||
valid inside the commit handler); windows with subsurface content
|
||||
composite at overlay-draw time from live textures instead, so every
|
||||
commit side -- the toplevel commit, each subsurface's commit, a sync
|
||||
child's arrival -- only has to repaint while the overlay is held. */
|
||||
static void thumb_capture(struct kick_window *w) {
|
||||
if (!w || !opt_overlay_previews) return;
|
||||
struct wlr_surface *s = window_surface(w);
|
||||
if (!s) return;
|
||||
if (!window_has_subsurfaces(w)) {
|
||||
struct wlr_buffer *b = s->current.buffer;
|
||||
thumb_capture_single(w, s, b);
|
||||
return;
|
||||
}
|
||||
thumb_capture_tree(w);
|
||||
}
|
||||
|
||||
static void thumb_sub_wire(struct kick_window *w, struct wlr_surface *s);
|
||||
|
||||
static void thumb_sub_commit(struct wl_listener *listener, void *data) {
|
||||
(void)data;
|
||||
struct kick_thumb_sub *sub = wl_container_of(listener, sub, commit);
|
||||
thumb_capture(sub->w);
|
||||
}
|
||||
|
||||
static void thumb_sub_destroy(struct wl_listener *listener, void *data) {
|
||||
(void)data;
|
||||
struct kick_thumb_sub *sub = wl_container_of(listener, sub, destroy);
|
||||
thumb_sub_release(sub);
|
||||
}
|
||||
|
||||
static void thumb_sub_new(struct wl_listener *listener, void *data) {
|
||||
struct kick_thumb_sub *sub = wl_container_of(listener, sub,
|
||||
new_subsurface);
|
||||
/* new_subsurface carries the fresh wlr_subsurface. */
|
||||
struct wlr_subsurface *wsub = data;
|
||||
if (!wsub || !wsub->surface) return;
|
||||
thumb_sub_wire(sub->w, wsub->surface);
|
||||
/* The tree gained a surface: repaint while the overlay is held so the
|
||||
new content appears without waiting for the next commit. */
|
||||
thumb_capture(sub->w);
|
||||
}
|
||||
|
||||
static bool thumb_sub_wired(struct kick_window *w, struct wlr_surface *s) {
|
||||
struct kick_thumb_sub *sub;
|
||||
wl_list_for_each(sub, &w->thumb_subs, link)
|
||||
if (sub->surface == s) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Watch `s` (and, via the walk in thumb_sub_wire_tree, its existing
|
||||
children) for thumbnail commits. Surfaces created later arrive through
|
||||
new_subsurface, which wlroots emits on the parent at their first
|
||||
commit. */
|
||||
static void thumb_sub_wire(struct kick_window *w, struct wlr_surface *s) {
|
||||
if (!w || !s || thumb_sub_wired(w, s)) return;
|
||||
struct kick_thumb_sub *sub = calloc(1, sizeof(*sub));
|
||||
if (!sub) return;
|
||||
sub->w = w;
|
||||
sub->surface = s;
|
||||
sub->commit.notify = thumb_sub_commit;
|
||||
wl_signal_add(&s->events.commit, &sub->commit);
|
||||
sub->destroy.notify = thumb_sub_destroy;
|
||||
wl_signal_add(&s->events.destroy, &sub->destroy);
|
||||
sub->new_subsurface.notify = thumb_sub_new;
|
||||
wl_signal_add(&s->events.new_subsurface, &sub->new_subsurface);
|
||||
wl_list_insert(w->thumb_subs.prev, &sub->link);
|
||||
}
|
||||
|
||||
static void thumb_wire_iter(struct wlr_surface *surface, int sx, int sy,
|
||||
void *data) {
|
||||
(void)sx;
|
||||
(void)sy;
|
||||
struct kick_window *w = data;
|
||||
/* The main surface already has the window's own commit listener:
|
||||
watching it twice would snapshot every frame twice. */
|
||||
if (surface == window_surface(w)) return;
|
||||
thumb_sub_wire(w, surface);
|
||||
}
|
||||
|
||||
static void thumb_sub_wire_tree(struct kick_window *w) {
|
||||
struct wlr_surface *s = window_surface(w);
|
||||
if (!s) return;
|
||||
wlr_surface_for_each_surface(s, thumb_wire_iter, w);
|
||||
}
|
||||
|
||||
static struct wlr_texture *overlay_thumb_tex(struct kick_window *w) {
|
||||
if (!w || !server.renderer) return NULL;
|
||||
if (w->thumb_src == THUMB_SRC_LIVE) {
|
||||
|
|
@ -2841,7 +3070,15 @@ static struct wlr_texture *overlay_thumb_tex(struct kick_window *w) {
|
|||
} else {
|
||||
return w->thumb_tex;
|
||||
}
|
||||
if (!tex) return w->thumb_tex; /* keep the previous frame; retry later */
|
||||
if (!tex) {
|
||||
/* A persistent NULL here is a black thumbnail (e.g. a dma-buf the
|
||||
renderer cannot import): log once per window so KICK_LOG=2 shows
|
||||
which tab never produced a texture. */
|
||||
if (!w->thumb_tex)
|
||||
kick_log(KICK_LOG_DEBUG, "[thumb] import failed app=%d src=%d\n",
|
||||
tab_index(w->tab), (int)w->thumb_src);
|
||||
return w->thumb_tex; /* keep the previous frame; retry later */
|
||||
}
|
||||
if (w->thumb_tex)
|
||||
wlr_texture_destroy(w->thumb_tex);
|
||||
w->thumb_tex = tex;
|
||||
|
|
@ -2856,6 +3093,12 @@ static void overlay_pass_thumb(struct wlr_render_pass *pass,
|
|||
struct kick_window *w,
|
||||
int tx, int ty, int tw, int th, float alpha) {
|
||||
overlay_pass_rect(pass, tx, ty, tw, th, 0.0f, 0.0f, 0.0f, 1.0f);
|
||||
/* Subsurface content (e.g. Firefox) never sits in the main surface's
|
||||
buffer: composite the live tree instead of the single snapshot. */
|
||||
if (window_has_subsurfaces(w)) {
|
||||
overlay_pass_tree_thumb(pass, w, tx, ty, tw, th, alpha);
|
||||
return;
|
||||
}
|
||||
struct wlr_texture *tex = overlay_thumb_tex(w);
|
||||
if (!tex || tex->width == 0 || tex->height == 0) return;
|
||||
int dw = tw, dh = (int)((int64_t)tw * tex->height / tex->width);
|
||||
|
|
@ -4940,6 +5183,7 @@ static struct kick_window *window_alloc(struct kick_tab *tab) {
|
|||
struct kick_window *w = calloc(1, sizeof(*w));
|
||||
if (!w) return NULL;
|
||||
w->tab = tab;
|
||||
wl_list_init(&w->thumb_subs);
|
||||
wl_list_insert(tab->windows.prev, &w->link);
|
||||
/* New windows start least-recent: background arrivals must not hijack
|
||||
Alt+Tab. The window bubbles to the front when it becomes current
|
||||
|
|
@ -4994,6 +5238,10 @@ static void claim_xdg_window(struct kick_tab *tab, struct kick_window *w,
|
|||
w->commit.notify = handle_toplevel_commit;
|
||||
wl_signal_add(&toplevel->base->surface->events.commit, &w->commit);
|
||||
|
||||
/* Subsurface paints bypass the toplevel commit: watch the whole tree
|
||||
so --overlay-previews thumbnails track subsurface clients too. */
|
||||
thumb_sub_wire_tree(w);
|
||||
|
||||
w->set_title.notify = handle_window_meta;
|
||||
wl_signal_add(&toplevel->events.set_title, &w->set_title);
|
||||
w->set_app_id.notify = handle_window_meta;
|
||||
|
|
|
|||
|
|
@ -6622,6 +6622,41 @@ def test_standalone_headless_overlay_previews():
|
|||
_assert_alive(s)
|
||||
|
||||
|
||||
def test_standalone_headless_overlay_previews_subsurface():
|
||||
"""A window painted through a subsurface (transparent main surface, color
|
||||
in the subsurface -- the Firefox pattern) shows its color in the overlay
|
||||
thumbnail, not black."""
|
||||
with _run_standalone(
|
||||
{}, "--overlay-previews",
|
||||
"%s --subsurface '#00ff00' '#000000'" % WL, "%s '#0000ff'" % WL,
|
||||
wait="any_frame") as s:
|
||||
s.wait_surface_mapped(0)
|
||||
s.wait_surface_mapped(1)
|
||||
_, green = _dominant_counts(s, s.dump_host())
|
||||
assert green < 500, \
|
||||
f"hidden subsurface tab visible without overlay: {green}"
|
||||
s.mods(ALT)
|
||||
try:
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
_, green = _dominant_counts(s, s.dump_host())
|
||||
if green > 2000:
|
||||
break
|
||||
time.sleep(0.2)
|
||||
assert green > 2000, \
|
||||
"no green thumbnail for subsurface window while Alt held"
|
||||
finally:
|
||||
s.mods(0)
|
||||
# The scene itself composites the subsurface: cycling to the tab
|
||||
# shows green in the main view, proving the bug was thumbnail-only.
|
||||
s.cycle_ack(+1)
|
||||
s.wait_active_frame()
|
||||
_, green = _dominant_counts(s, s.dump_host())
|
||||
assert green > 2000, \
|
||||
f"subsurface window not green in main view: {green}"
|
||||
_assert_alive(s)
|
||||
|
||||
|
||||
def test_standalone_headless_overlay_previews_live():
|
||||
"""Preview thumbnails track window commits while the overlay is held:
|
||||
a window mapping mid-hold (the delayed --extra) gains a live thumbnail
|
||||
|
|
|
|||
118
tests/wltest.c
118
tests/wltest.c
|
|
@ -513,7 +513,24 @@ static struct xdg_surface *ex_xdg;
|
|||
static struct xdg_toplevel *ex_toplevel;
|
||||
static struct wl_buffer *ex_buffer;
|
||||
|
||||
/* --subsurface #RRGGBB: paint the window through a subsurface instead of the
|
||||
main surface. The main surface commits a fully transparent buffer while a
|
||||
single subsurface, covering the whole window, carries the solid color --
|
||||
the pattern real clients (e.g. Firefox) use when their content lives off
|
||||
the main surface. The scene shows the color; a thumbnail that only reads
|
||||
the main surface sees black. */
|
||||
static bool subsurface_mode;
|
||||
static uint8_t SUB_PIXEL[4] = { 0x00, 0xff, 0x00, 0xff }; /* RGBA */
|
||||
static struct wl_subcompositor *subcomp;
|
||||
static struct wl_surface *sub_surface;
|
||||
static struct wl_subsurface *subsurface;
|
||||
static struct wl_buffer *sub_buffer;
|
||||
|
||||
static void create_buffer(void);
|
||||
static struct wl_shm_pool *make_pool(size_t size, int *fd_out);
|
||||
static struct wl_buffer *make_solid_buffer(uint8_t r, uint8_t g, uint8_t b,
|
||||
uint8_t a, int32_t format);
|
||||
static void subsurface_attach(void);
|
||||
static void extra_open(void);
|
||||
static void request_constraint(void);
|
||||
static void pulse_arm(void);
|
||||
|
|
@ -819,14 +836,81 @@ static const struct xdg_wm_base_listener wm_base_listener = {
|
|||
.ping = xdg_wm_base_ping,
|
||||
};
|
||||
|
||||
/* Fill a W×H SHM buffer with one RGBA pixel (B/G/R/A byte order). */
|
||||
static struct wl_buffer *make_solid_buffer(uint8_t r, uint8_t g, uint8_t b,
|
||||
uint8_t a, int32_t format) {
|
||||
size_t stride = (size_t)W * 4;
|
||||
size_t size = stride * (size_t)H;
|
||||
int fd;
|
||||
struct wl_shm_pool *pool = make_pool(size, &fd);
|
||||
if (!pool) {
|
||||
fprintf(stderr, "wltest: subsurface: shm pool failed\n");
|
||||
exit(1);
|
||||
}
|
||||
uint8_t *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
fprintf(stderr, "wltest: subsurface: mmap failed\n");
|
||||
exit(1);
|
||||
}
|
||||
for (int y = 0; y < H; y++) {
|
||||
uint8_t *row = data + (size_t)y * stride;
|
||||
for (int x = 0; x < W; x++) {
|
||||
row[x*4+0] = b;
|
||||
row[x*4+1] = g;
|
||||
row[x*4+2] = r;
|
||||
row[x*4+3] = a;
|
||||
}
|
||||
}
|
||||
munmap(data, size);
|
||||
close(fd);
|
||||
struct wl_buffer *buf = wl_shm_pool_create_buffer(pool, 0, W, H,
|
||||
(int32_t)stride, format);
|
||||
wl_shm_pool_destroy(pool);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* --subsurface attach: transparent main buffer plus the color subsurface.
|
||||
The subsurface stays synchronous (the default), so the parent commit
|
||||
applies both atomically and the compositor sees one coherent frame. */
|
||||
static void subsurface_attach(void) {
|
||||
if (!subcomp) {
|
||||
fprintf(stderr, "wltest: subsurface: wl_subcompositor not advertised\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!subsurface) {
|
||||
sub_surface = wl_compositor_create_surface(compositor);
|
||||
subsurface = wl_subcompositor_get_subsurface(subcomp, sub_surface,
|
||||
surface);
|
||||
wl_subsurface_set_position(subsurface, 0, 0);
|
||||
wl_subsurface_place_above(subsurface, surface);
|
||||
}
|
||||
if (sub_buffer)
|
||||
wl_buffer_destroy(sub_buffer);
|
||||
sub_buffer = make_solid_buffer(SUB_PIXEL[0], SUB_PIXEL[1], SUB_PIXEL[2],
|
||||
SUB_PIXEL[3], WL_SHM_FORMAT_ARGB8888);
|
||||
wl_surface_attach(sub_surface, sub_buffer, 0, 0);
|
||||
wl_surface_damage_buffer(sub_surface, 0, 0, W, H);
|
||||
wl_surface_commit(sub_surface);
|
||||
/* Main surface: fully transparent (alpha 0 everywhere). */
|
||||
if (buffer)
|
||||
wl_buffer_destroy(buffer);
|
||||
buffer = make_solid_buffer(0, 0, 0, 0, WL_SHM_FORMAT_ARGB8888);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(surface, 0, 0, W, H);
|
||||
}
|
||||
|
||||
static void xdg_surface_configure(void *data, struct xdg_surface *xs, uint32_t serial) {
|
||||
(void)data;
|
||||
xdg_surface_ack_configure(xs, serial);
|
||||
create_buffer();
|
||||
if (single_pixel_mode && viewport)
|
||||
wp_viewport_set_destination(viewport, W, H);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(surface, 0, 0, W, H);
|
||||
if (subsurface_mode) {
|
||||
subsurface_attach();
|
||||
} else {
|
||||
create_buffer();
|
||||
if (single_pixel_mode && viewport)
|
||||
wp_viewport_set_destination(viewport, W, H);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(surface, 0, 0, W, H);
|
||||
}
|
||||
if (pulse_ms && !pulse_cb) {
|
||||
/* Start the pulse loop once the window exists; the base color is
|
||||
whatever the command line set. Armed before the commit below
|
||||
|
|
@ -950,6 +1034,9 @@ static void registry_global(void *data, struct wl_registry *reg, uint32_t name,
|
|||
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||
shm = wl_registry_bind(reg, name, &wl_shm_interface,
|
||||
version < 1 ? version : 1);
|
||||
} else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
|
||||
subcomp = wl_registry_bind(reg, name, &wl_subcompositor_interface,
|
||||
version < 1 ? version : 1);
|
||||
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
|
||||
/* xdg_popup.reposition needs wm_base (and hence popup) version
|
||||
3+, but the default v1 bind is kept for every other mode so no
|
||||
|
|
@ -3644,8 +3731,10 @@ static void print_usage(const char *argv0) {
|
|||
"[--output-mgmt [--output-mgmt-out <file>] "
|
||||
"[--output-mgmt-size <W>x<H>]] "
|
||||
"#RRGGBB [WIDTHxHEIGHT]\n"
|
||||
" %s shows a solid-color xdg_toplevel, e.g. #ff0000\n"
|
||||
" --extra also opens a second toplevel from the same client\n"
|
||||
" %s shows a solid-color xdg_toplevel, e.g. #ff0000\n"
|
||||
" --extra also opens a second toplevel from the same client\n"
|
||||
" --subsurface paints the color through a subsurface over\n"
|
||||
" a transparent main surface (subsurface-client proof)\n"
|
||||
" --pulse MS repaints alternating main color/black every\n"
|
||||
" MS ms on frame callbacks (a live source for previews)\n"
|
||||
" --cursor-shape requests that cursor-shape-v1 shape on "
|
||||
|
|
@ -3761,6 +3850,21 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
want_extra = true;
|
||||
i += 2;
|
||||
} else if (strcmp(argv[i], "--subsurface") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fprintf(stderr, "wltest: --subsurface needs a value\n");
|
||||
return 1;
|
||||
}
|
||||
const char *x = argv[i + 1];
|
||||
if (*x == '#') x++;
|
||||
if (strlen(x) != 6 || sscanf(x, "%2hhx%2hhx%2hhx",
|
||||
&SUB_PIXEL[0], &SUB_PIXEL[1], &SUB_PIXEL[2]) != 3) {
|
||||
fprintf(stderr, "wltest: bad subsurface color '%s'\n", argv[i + 1]);
|
||||
return 1;
|
||||
}
|
||||
SUB_PIXEL[3] = 0xff;
|
||||
subsurface_mode = true;
|
||||
i += 2;
|
||||
} else if (strcmp(argv[i], "--dialog-modal") == 0) {
|
||||
dialog_modal = true;
|
||||
i += 1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue