Composite popups and fix sub-menu rendering
This commit is contained in:
parent
efecacacb1
commit
0b6a9eef75
1 changed files with 173 additions and 39 deletions
212
tabbed-wlroots.c
212
tabbed-wlroots.c
|
|
@ -101,6 +101,7 @@ struct tabbed_server {
|
|||
|
||||
struct wl_listener new_output;
|
||||
struct wl_listener new_toplevel;
|
||||
struct wl_listener new_popup;
|
||||
struct wl_listener frame;
|
||||
|
||||
struct wlr_xdg_toplevel *active;
|
||||
|
|
@ -142,6 +143,7 @@ struct tabbed_host {
|
|||
GLuint gl_program;
|
||||
GLint gl_pos_loc, gl_uv_loc, gl_tex_loc;
|
||||
GLuint import_tex;
|
||||
GLuint popup_tex;
|
||||
};
|
||||
|
||||
static struct tabbed_server server;
|
||||
|
|
@ -229,6 +231,14 @@ static bool gl_setup(void) {
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
glGenTextures(1, &host.popup_tex);
|
||||
glBindTexture(GL_TEXTURE_2D, host.popup_tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -546,6 +556,117 @@ static struct tabbed_app *app_for_toplevel(struct wlr_xdg_toplevel *toplevel) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
struct tabbed_popup {
|
||||
struct wlr_xdg_surface *xdg_surface;
|
||||
struct wlr_scene_tree *scene;
|
||||
struct wl_list link;
|
||||
struct wl_listener commit;
|
||||
struct wl_listener destroy;
|
||||
struct wl_listener reposition;
|
||||
};
|
||||
|
||||
/* Popups are also drawn by us (host-side) for the presented frame, because
|
||||
wlroots' own premultiplied-alpha rendering of these ARGB menus produces
|
||||
nothing in this nested EGL pipeline. This list tracks the live ones. */
|
||||
static struct wl_list popups = { &popups, &popups };
|
||||
|
||||
/* Enough of the host window for popups to be positioned within; open menus
|
||||
may exceed the toplevel but we keep them inside the visible area. */
|
||||
static void popup_usable_box(struct wlr_box *box) {
|
||||
box->x = 0;
|
||||
box->y = 0;
|
||||
if (server.output) {
|
||||
box->width = server.output->width;
|
||||
box->height = server.output->height;
|
||||
} else {
|
||||
box->width = host.width;
|
||||
box->height = host.height;
|
||||
}
|
||||
}
|
||||
|
||||
/* Menu popups (e.g. galculator's) submit ARGB buffers that wlroots renders
|
||||
with premultiplied-alpha blending, which produces nothing in this nested
|
||||
EGL pipeline. We therefore draw them ourselves into the presented frame
|
||||
(see composite_popups in blit_scene_buffer). */
|
||||
static void popup_handle_commit(struct wl_listener *listener, void *data) {
|
||||
(void)data;
|
||||
struct tabbed_popup *tp = wl_container_of(listener, tp, commit);
|
||||
struct wlr_xdg_surface *xdg_surface = tp->xdg_surface;
|
||||
/* The client cannot map/render the popup until it receives its initial
|
||||
configure, which wlroots only sends via wlr_xdg_popup_unconstrain_from
|
||||
(which also adjusts geometry within the usable area). initial_commit is
|
||||
true on the first commit; later commits are ignored. */
|
||||
if (xdg_surface->initial_commit && xdg_surface->popup) {
|
||||
struct wlr_box box;
|
||||
popup_usable_box(&box);
|
||||
wlr_xdg_popup_unconstrain_from_box(xdg_surface->popup, &box);
|
||||
}
|
||||
}
|
||||
|
||||
static void popup_handle_reposition(struct wl_listener *listener, void *data) {
|
||||
(void)data;
|
||||
struct tabbed_popup *tp = wl_container_of(listener, tp, reposition);
|
||||
if (tp->xdg_surface->popup) {
|
||||
struct wlr_box box;
|
||||
popup_usable_box(&box);
|
||||
wlr_xdg_popup_unconstrain_from_box(tp->xdg_surface->popup, &box);
|
||||
}
|
||||
}
|
||||
|
||||
static void popup_handle_destroy(struct wl_listener *listener, void *data) {
|
||||
(void)data;
|
||||
struct tabbed_popup *tp = wl_container_of(listener, tp, destroy);
|
||||
wl_list_remove(&tp->link);
|
||||
wl_list_remove(&tp->commit.link);
|
||||
wl_list_remove(&tp->destroy.link);
|
||||
wl_list_remove(&tp->reposition.link);
|
||||
free(tp);
|
||||
}
|
||||
|
||||
static void create_popup(struct wlr_xdg_popup *popup,
|
||||
struct wlr_scene_tree *parent_tree) {
|
||||
struct wlr_xdg_surface *xdg_surface = popup->base;
|
||||
struct tabbed_popup *tp = calloc(1, sizeof(*tp));
|
||||
if (!tp) return;
|
||||
tp->xdg_surface = xdg_surface;
|
||||
tp->scene = wlr_scene_xdg_surface_create(parent_tree, xdg_surface);
|
||||
wl_list_insert(&popups, &tp->link);
|
||||
/* wlr_scene_xdg_surface_create destroys the node when the surface is
|
||||
destroyed, so we only track positioning/reposition listeners here. */
|
||||
tp->commit.notify = popup_handle_commit;
|
||||
wl_signal_add(&xdg_surface->surface->events.commit, &tp->commit);
|
||||
tp->destroy.notify = popup_handle_destroy;
|
||||
/* Listen on the popup's own destroy: wlroots emits it (then asserts its
|
||||
event lists are empty) before the popup is freed. */
|
||||
wl_signal_add(&popup->events.destroy, &tp->destroy);
|
||||
tp->reposition.notify = popup_handle_reposition;
|
||||
wl_signal_add(&popup->events.reposition, &tp->reposition);
|
||||
/* remember the scene tree so a submenu popup can find its parent's tree */
|
||||
xdg_surface->data = tp;
|
||||
}
|
||||
|
||||
static void handle_new_popup(struct wl_listener *listener, void *data) {
|
||||
(void)listener;
|
||||
struct wlr_xdg_popup *popup = data;
|
||||
|
||||
/* Popups position themselves relative to their immediate parent surface,
|
||||
so place each popup under its parent's scene tree. */
|
||||
struct wlr_scene_tree *parent_tree = NULL;
|
||||
struct wlr_xdg_surface *parent =
|
||||
wlr_xdg_surface_try_from_wlr_surface(popup->parent);
|
||||
if (parent) {
|
||||
if (parent->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
|
||||
struct tabbed_app *app = app_for_toplevel(parent->toplevel);
|
||||
if (app) parent_tree = app->tree;
|
||||
} else if (parent->role == WLR_XDG_SURFACE_ROLE_POPUP) {
|
||||
struct tabbed_popup *tp = parent->data;
|
||||
if (tp) parent_tree = tp->scene;
|
||||
}
|
||||
}
|
||||
if (!parent_tree) return;
|
||||
create_popup(popup, parent_tree);
|
||||
}
|
||||
|
||||
static void set_active_surface(struct wlr_xdg_toplevel *toplevel) {
|
||||
if (server.active == toplevel) return;
|
||||
|
||||
|
|
@ -1119,41 +1240,58 @@ static bool import_scene_buffer(struct wlr_buffer *buf, GLuint tex) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/* Dump the host window (the EGL surface we present) to a PPM, on demand.
|
||||
Triggered with `kill -USR1 <pid>` so the current frame can be inspected
|
||||
without any screen capture. */
|
||||
static volatile sig_atomic_t dump_requested = 0;
|
||||
/* Draw one popup surface buffer into the presented window at its on-screen
|
||||
position. This bypasses wlroots' scene rendering for popups, whose
|
||||
premultiplied-alpha blend paints nothing in our nested EGL pipeline.
|
||||
|
||||
static void sigusr1_handler(int sig) {
|
||||
(void)sig;
|
||||
dump_requested = 1;
|
||||
Each popup surface is composited exactly once per frame. A sub-menu is a
|
||||
popup nested under its parent menu's scene tree, so it is reachable both
|
||||
from its own tree and from the parent menu's tree; the surfaces-drawn array
|
||||
prevents it being drawn twice. Position uses wlr_scene_node_coords (the
|
||||
true absolute offset, including every ancestor), because the iterator's
|
||||
sx/sy only sums offsets from its own tree root and would place a nested
|
||||
popup too high/left by the missing ancestor offsets. */
|
||||
struct popup_draw_ctx {
|
||||
int win_w, win_h;
|
||||
struct wlr_surface **drawn;
|
||||
int ndrawn;
|
||||
};
|
||||
|
||||
static void popup_draw_cb(struct wlr_scene_buffer *buffer,
|
||||
int sx, int sy, void *user_data) {
|
||||
struct popup_draw_ctx *ctx = user_data;
|
||||
if (!buffer->buffer) return;
|
||||
struct wlr_scene_surface *ss = wlr_scene_surface_try_from_buffer(buffer);
|
||||
if (!ss) return;
|
||||
for (int i = 0; i < ctx->ndrawn; i++) {
|
||||
if (ctx->drawn[i] == ss->surface) return;
|
||||
}
|
||||
ctx->drawn[ctx->ndrawn++] = ss->surface;
|
||||
(void)sx; (void)sy;
|
||||
int lx = 0, ly = 0;
|
||||
wlr_scene_node_coords(&buffer->node, &lx, &ly);
|
||||
if (!import_scene_buffer(buffer->buffer, host.popup_tex)) return;
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
draw_texture_rect(host.popup_tex, buffer->buffer->width,
|
||||
buffer->buffer->height,
|
||||
0, 0, buffer->buffer->width, buffer->buffer->height,
|
||||
lx, ly, ctx->win_w, ctx->win_h);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
static void dump_window(int seq) {
|
||||
int w = host.width, h = host.height;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
size_t npix = (size_t)w * h;
|
||||
uint8_t *px = malloc(npix * 4);
|
||||
if (!px) return;
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, px);
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/tmp/window-%04d.ppm", seq);
|
||||
FILE *f = fopen(path, "w");
|
||||
if (f) {
|
||||
fprintf(f, "P6\n%d %d\n255\n", w, h);
|
||||
for (int y = h - 1; y >= 0; y--) {
|
||||
const uint8_t *row = px + (size_t)y * w * 4;
|
||||
for (int x = 0; x < w; x++) {
|
||||
fputc(row[x * 4], f);
|
||||
fputc(row[x * 4 + 1], f);
|
||||
fputc(row[x * 4 + 2], f);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
fprintf(stderr, "[dump] wrote %s (%dx%d)\n", path, w, h);
|
||||
}
|
||||
free(px);
|
||||
static void composite_popups(void) {
|
||||
struct wlr_surface *drawn[64];
|
||||
struct popup_draw_ctx ctx = {
|
||||
.win_w = host.width,
|
||||
.win_h = host.height,
|
||||
.drawn = drawn,
|
||||
.ndrawn = 0,
|
||||
};
|
||||
struct tabbed_popup *tp;
|
||||
wl_list_for_each(tp, &popups, link)
|
||||
wlr_scene_node_for_each_buffer(&tp->scene->node,
|
||||
popup_draw_cb, &ctx);
|
||||
}
|
||||
|
||||
static void blit_scene_buffer(struct wlr_buffer *buf) {
|
||||
|
|
@ -1169,11 +1307,7 @@ static void blit_scene_buffer(struct wlr_buffer *buf) {
|
|||
0, 0, buf->width, buf->height, 0, 0, w, h);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
if (dump_requested) {
|
||||
dump_requested = 0;
|
||||
static int seq;
|
||||
dump_window(++seq);
|
||||
}
|
||||
composite_popups();
|
||||
eglSwapBuffers(host.egl_display, host.egl_surface);
|
||||
}
|
||||
|
||||
|
|
@ -1258,8 +1392,6 @@ int main(int argc, char *argv[]) {
|
|||
sigaction(SIGSEGV, &crash_sa, NULL);
|
||||
sigaction(SIGABRT, &crash_sa, NULL);
|
||||
signal(SIGCHLD, sigchld_handler);
|
||||
signal(SIGUSR1, sigusr1_handler);
|
||||
|
||||
wlr_log_init(WLR_DEBUG, NULL);
|
||||
|
||||
server.wl_display = wl_display_create();
|
||||
|
|
@ -1325,6 +1457,8 @@ int main(int argc, char *argv[]) {
|
|||
wl_signal_add(&server.backend->events.new_output, &server.new_output);
|
||||
server.new_toplevel.notify = handle_new_toplevel;
|
||||
wl_signal_add(&server.xdg_shell->events.new_toplevel, &server.new_toplevel);
|
||||
server.new_popup.notify = handle_new_popup;
|
||||
wl_signal_add(&server.xdg_shell->events.new_popup, &server.new_popup);
|
||||
|
||||
/* headless output; new_output fires during backend start */
|
||||
host.width = 1280;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue