From e47b44ccd039b3947a2308a54ce53d7c037cbdcd Mon Sep 17 00:00:00 2001 From: Window Maker Date: Tue, 2 Jun 2026 10:41:30 +0200 Subject: [PATCH] decompose wl_backend.c: extract wl_framebuf.c --- src/Makefile.am | 3 +- src/backend/wayland/wl_backend.c | 706 +---------------------------- src/backend/wayland/wl_framebuf.c | 729 ++++++++++++++++++++++++++++++ src/backend/wayland/wl_types.h | 15 + 4 files changed, 747 insertions(+), 706 deletions(-) create mode 100644 src/backend/wayland/wl_framebuf.c diff --git a/src/Makefile.am b/src/Makefile.am index 88af0e8..d7493c8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -165,7 +165,8 @@ wmaker_SOURCES += \ backend/wayland/wl_window.c \ backend/wayland/wl_props.c \ backend/wayland/wl_client.c \ - backend/wayland/wl_icccm.c + backend/wayland/wl_icccm.c \ + backend/wayland/wl_framebuf.c endif if USE_NESTED_FUNC diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 35b0745..c104c30 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -3976,710 +3976,6 @@ wl_show_crash_dialog(int sig) /* ------------------------------------------------------------------ */ /* Frame buffer (pixman + wlr_scene_buffer) */ -/* ------------------------------------------------------------------ */ - -static void frame_buf_destroy(struct wlr_buffer *wlr_buf) -{ - struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base); - if (fb->image) - pixman_image_unref(fb->image); - wfree(fb); -} - -static bool frame_buf_begin_data_ptr_access(struct wlr_buffer *wlr_buf, - uint32_t flags, void **data, uint32_t *format, size_t *stride) -{ - struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base); - (void)flags; - *data = pixman_image_get_data(fb->image); - *format = DRM_FORMAT_ARGB8888; - *stride = (size_t)pixman_image_get_stride(fb->image); - return true; -} - -static void frame_buf_end_data_ptr_access(struct wlr_buffer *wlr_buf) -{ - (void)wlr_buf; -} - -const struct wlr_buffer_impl frame_buf_impl = { - .destroy = frame_buf_destroy, - .begin_data_ptr_access = frame_buf_begin_data_ptr_access, - .end_data_ptr_access = frame_buf_end_data_ptr_access, -}; - -struct wl_frame_buf * -frame_buf_find(WNativeWindow id) -{ - struct wl_frame_buf *fb; - wl_list_for_each(fb, &wl_state.frame_list, link) { - if (fb->id == id) return fb; - } - return NULL; -} - -/* - * fb_scene_buf_attach / fb_tree_attach - * - * Register destroy listeners on a scene_buf or tree so that fb->scene_buf - * (or fb->tree) is NULL'd when wlroots auto-destroys the node — typically - * via cascade when an ancestor scene_tree is destroyed. Without this, - * follow-up cleanup paths (wl_view_destroy_backing_impl, wl_frame_destroy) - * call wlr_scene_node_destroy on a freed node and segfault inside - * wl_list_remove against a zeroed list link. - */ -static void fb_scene_buf_destroy_handler(struct wl_listener *l, void *d) -{ - (void)d; - struct wl_frame_buf *fb = wl_container_of(l, fb, scene_buf_destroy); - fb->scene_buf = NULL; - wl_list_remove(&fb->scene_buf_destroy.link); -} -static void fb_tree_destroy_handler(struct wl_listener *l, void *d) -{ - (void)d; - struct wl_frame_buf *fb = wl_container_of(l, fb, tree_destroy); - fb->tree = NULL; - /* The tree's destroy cascades into scene_buf — its destroy handler - * will fire too and NULL fb->scene_buf separately. */ - wl_list_remove(&fb->tree_destroy.link); -} -void fb_attach_scene_buf_listener(struct wl_frame_buf *fb) -{ - if (!fb || !fb->scene_buf) return; - fb->scene_buf_destroy.notify = fb_scene_buf_destroy_handler; - wl_signal_add(&fb->scene_buf->node.events.destroy, - &fb->scene_buf_destroy); -} -static void fb_attach_tree_listener(struct wl_frame_buf *fb) -{ - if (!fb || !fb->tree) return; - fb->tree_destroy.notify = fb_tree_destroy_handler; - wl_signal_add(&fb->tree->node.events.destroy, &fb->tree_destroy); -} - -/* Recursively update fb->x, fb->y for all descendants of `parent`, used - * to keep absolute-position tracking in sync after the parent moves. - * Hit-testing (frame_buf_at, frame_buf_button_at) and click coordinate - * translation depend on these fields being current. */ -void -frame_buf_update_children_abs(WNativeWindow parent_id) -{ - struct wl_frame_buf *parent = frame_buf_find(parent_id); - if (!parent) return; - struct wl_frame_buf *child; - wl_list_for_each(child, &wl_state.frame_list, link) { - if (child->parent_id == parent_id) { - child->x = parent->x + child->rel_x; - child->y = parent->y + child->rel_y; - /* Recurse into grandchildren */ - frame_buf_update_children_abs(child->id); - } - } - /* Also update toplevel views parented to this frame */ - struct wl_toplevel_view *cv; - wl_list_for_each(cv, &wl_state.toplevel_list, link) { - if (cv->frame_id == parent_id) { - cv->x = parent->x + cv->frame_rel_x; - cv->y = parent->y + cv->frame_rel_y; - } - } -} - -/* Hit-test: find the frame_buf under (px, py) that should receive the event. - * - * Returns the most-specific (innermost) frame_buf whose rendered pixels - * are topmost at (px, py), so the WM event layer receives the correct - * sub-window ID (titlebar, resizebar, button) needed to trigger move/resize. - * - * Scene-graph structure (level-tree model): - * - * scene->tree - * level_trees[N] <- level bucket - * fb->tree <- top-level frame grouping tree - * fb->scene_buf <- full-frame decoration pixels - * child_fb->scene_buf <- titlebar / resizebar sub-frames - * v->scene_node <- client surface (returns NULL) - * - * Strategy: - * 1. wlr_scene_node_at gives the topmost rendered node. - * 2. First check if that exact node is a child frame_buf's scene_buf - * (titlebar, resizebar, button) — if so, return it directly. - * 3. Walk up to the fb->tree level (parent is a level_tree bucket). - * 4. Match that window-root node to a top-level frame_buf's tree. - * 5. If it was a client surface (v->scene_node), return NULL. */ -static struct wl_frame_buf * -frame_buf_at(int px, int py) -{ - if (!wl_state.scene) return NULL; - - double sx, sy; - struct wlr_scene_node *node = wlr_scene_node_at( - &wl_state.scene->tree.node, (double)px, (double)py, &sx, &sy); - if (!node) return NULL; - - struct wl_frame_buf *fb; - - /* Step 2: exact match — is the topmost node a child frame_buf's - * scene_buf (no tree wrapper)? This covers titlebars, resizebars, - * and button sub-frames, which are what the WM needs for move/resize. */ - wl_list_for_each(fb, &wl_state.frame_list, link) { - if (!fb->tree && fb->scene_buf && &fb->scene_buf->node == node) - return fb; - } - - /* Step 3: walk up until the parent is a level_tree bucket. - * The node we stop at is the fb->tree of a top-level frame, or - * a v->scene_node sitting directly in a level tree (unframed). */ - struct wlr_scene_node *n = node; - while (n->parent - && &n->parent->node != &wl_state.scene->tree.node) { - int _at_level = 0, _li; - for (_li = 0; _li < WL_NUM_LEVEL_BUCKETS; _li++) { - if (wl_state.level_trees[_li] && - n->parent == wl_state.level_trees[_li]) { - _at_level = 1; break; - } - } - if (_at_level) break; - n = &n->parent->node; - } - - /* Step 4: match window-root node to a top-level frame_buf. */ - wl_list_for_each(fb, &wl_state.frame_list, link) { - if (fb->tree && &fb->tree->node == n) - return fb; - } - - /* Step 5: match a tree-less frame_buf (WINGs dialog) whose scene_buf - * is a direct child of a level_tree bucket. */ - wl_list_for_each(fb, &wl_state.frame_list, link) { - if (!fb->tree && fb->scene_buf && &fb->scene_buf->node == n) - return fb; - } - - /* Step 6: topmost node was a client surface — not a WM frame. */ - return NULL; -} - -/* - * frame_buf_button_at — coordinate-based hit-test for virtual button frame_bufs. - * - * Buttons are composited into the titlebar buffer: their scene_buf nodes are - * in the scene graph but carry no committed buffer (scene_buf->buffer == NULL) - * and are therefore invisible to wlr_scene_node_at. We find them by walking - * the frame_list and comparing absolute screen coordinates. - * - * Returns the button fb whose absolute rect encloses (px,py) with the - * smallest area (most specific), or NULL if no button is under the pointer. - */ -static struct wl_frame_buf * -frame_buf_button_at(int px, int py) -{ - struct wl_frame_buf *best = NULL; - struct wl_frame_buf *fb; - wl_list_for_each(fb, &wl_state.frame_list, link) { - if (!fb->parent_id) continue; /* top-level root */ - if (!fb->scene_buf) continue; /* no scene node */ - if (!fb->scene_buf->node.enabled) continue; /* hidden */ - /* Walk up the scene tree — if any ancestor is disabled, this - * frame_buf is not actually visible (just its node enabled flag - * is set, but a parent tree was disabled e.g. by window_unmap). */ - struct wlr_scene_node *n = fb->scene_buf->node.parent - ? &fb->scene_buf->node.parent->node : NULL; - int ancestor_disabled = 0; - while (n) { - if (!n->enabled) { ancestor_disabled = 1; break; } - n = n->parent ? &n->parent->node : NULL; - } - if (ancestor_disabled) continue; - /* Buttons are small children of the titlebar with enabled scene_bufs. - * Skip large nodes (titlebars, resizebars) by size threshold. */ - if (px < fb->x || px >= fb->x + fb->width) continue; - if (py < fb->y || py >= fb->y + fb->height) continue; - /* Prefer the smallest (most specific) enclosing fb. */ - if (!best || (fb->width * fb->height) < (best->width * best->height)) - best = fb; - } - return best; -} - -/* - * frame_buf_hit — resolve the WM sub-window at (px,py). - * - * Tries button hit-testing first (buttons are invisible to the scene graph - * because their pixels are composited into the titlebar buffer), then falls - * back to the scene-graph-based frame_buf_at for titlebars, resizebars, and - * top-level frames. - */ -static struct wl_frame_buf * -frame_buf_hit(int px, int py) -{ - struct wl_frame_buf *fb = frame_buf_button_at(px, py); - return fb ? fb : frame_buf_at(px, py); -} - -/* ------------------------------------------------------------------ */ -/* Frame / window lifecycle */ -/* ------------------------------------------------------------------ */ - -static WNativeWindow -wl_frame_create_toplevel(WScreen *scr, - int x, int y, int width, int height, - int border_width, - int depth, void *visual, - unsigned long colormap, - WMPixel border_pixel) -{ - (void)border_width; (void)depth; (void)visual; - (void)colormap; (void)border_pixel; (void)scr; - - if (width <= 0) width = 1; - if (height <= 0) height = 1; - - struct wl_frame_buf *fb = wmalloc(sizeof(*fb)); - memset(fb, 0, sizeof(*fb)); - - fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, - width, height, NULL, 0); - if (!fb->image) { - wfree(fb); - return WNATIVE_NONE; - } - - wlr_buffer_init(&fb->base, &frame_buf_impl, width, height); - - fb->id = wl_state.next_synthetic_id++; - fb->x = x; - fb->y = y; - fb->width = width; - fb->height = height; - - /* Create scene buffer node under a per-window tree. - * The tree is positioned at (x,y) so all children use local offsets. - * Default to WL_LAYER_NORMAL; wl_stacking_set_level() will reparent - * to the correct bucket when AddToStackList() fires immediately after. */ - if (wl_state.scene) { - struct wlr_scene_tree *_parent = wl_state.level_trees[WL_LAYER_NORMAL] - ? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree; - fb->tree = wlr_scene_tree_create(_parent); - if (fb->tree) { - fb_attach_tree_listener(fb); - wlr_scene_node_set_position(&fb->tree->node, x, y); - fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base); - if (fb->scene_buf) { - fb_attach_scene_buf_listener(fb); - wlr_scene_node_set_position(&fb->scene_buf->node, 0, 0); - wlr_scene_node_set_enabled(&fb->scene_buf->node, false); - } - /* Tree starts disabled — wl_window_map enables it when the - * frame is logically mapped. Without this, decoration - * children (titlebar, buttons) of unmapped menus/dialogs - * appear as orphans because window_map_raised on those - * children enables their scene_bufs while the frame itself - * has not been mapped. */ - wlr_scene_node_set_enabled(&fb->tree->node, false); - } - } - - wl_list_insert(&wl_state.frame_list, &fb->link); - /* Register this frame_buf image in the WINGs backing table so - * WMDrawString() can resolve the window ID to a pixman_image_t - * (needed for miniwindow title drawing in update_icon_title). */ - W_RegisterBacking(fb->id, fb->image); - return fb->id; -} - -static WNativeWindow -wl_frame_create_child(WNativeWindow parent_win, WScreen *scr, - int x, int y, int width, int height) -{ - (void)scr; - - if (width <= 0) width = 1; - if (height <= 0) height = 1; - - /* Compute absolute position from parent */ - int abs_x = x, abs_y = y; - struct wl_frame_buf *parent = frame_buf_find(parent_win); - if (parent) { - abs_x = parent->x + x; - abs_y = parent->y + y; - } - - struct wl_frame_buf *fb = wmalloc(sizeof(*fb)); - memset(fb, 0, sizeof(*fb)); - - fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, - width, height, NULL, 0); - if (!fb->image) { - wfree(fb); - return WNATIVE_NONE; - } - - wlr_buffer_init(&fb->base, &frame_buf_impl, width, height); - - fb->id = wl_state.next_synthetic_id++; - fb->parent_id = parent_win; - fb->rel_x = x; - fb->rel_y = y; - fb->x = abs_x; - fb->y = abs_y; - fb->width = width; - fb->height = height; - - if (wl_state.scene) { - struct wlr_scene_tree *ptree = (parent && parent->tree) - ? parent->tree : &wl_state.scene->tree; - fb->scene_buf = wlr_scene_buffer_create(ptree, &fb->base); - if (fb->scene_buf) { - fb_attach_scene_buf_listener(fb); - wlr_scene_node_set_position(&fb->scene_buf->node, x, y); - wlr_scene_node_set_enabled(&fb->scene_buf->node, false); - } - } - - wl_list_insert(&wl_state.frame_list, &fb->link); - W_RegisterBacking(fb->id, fb->image); - return fb->id; -} - -static void -wl_frame_destroy(WScreen *scr, WNativeWindow win) -{ - (void)scr; - struct wl_frame_buf *fb = frame_buf_find(win); - if (!fb) return; - W_UnregisterBacking(fb->id); - /* Destroy the scene_buf and (if any) the tree. Without destroying - * the tree, leaked tree nodes accumulate in the scene graph and - * can cause stale rendering or hit-testing issues (e.g. ghost - * titlebars after a menu is closed). */ - if (fb->scene_buf) - wlr_scene_node_destroy(&fb->scene_buf->node); - if (fb->tree) - wlr_scene_node_destroy(&fb->tree->node); - wl_list_remove(&fb->link); - wlr_buffer_drop(&fb->base); -} - -static void -wl_frame_configure(WNativeWindow win, int x, int y, int width, int height) -{ - struct wl_frame_buf *fb = frame_buf_find(win); - if (!fb) return; - - /* Update position tracking */ - fb->rel_x = x; - fb->rel_y = y; - if (fb->parent_id) { - struct wl_frame_buf *parent = frame_buf_find(fb->parent_id); - if (parent) { - fb->x = parent->x + x; - fb->y = parent->y + y; - } else { - fb->x = x; - fb->y = y; - } - /* Child frame_buf: reposition scene_buf at relative offset within parent tree */ - if (fb->scene_buf) - wlr_scene_node_set_position(&fb->scene_buf->node, x, y); - } else { - fb->x = x; - fb->y = y; - /* Top-level frame: move the tree (children follow automatically) */ - if (fb->tree) - wlr_scene_node_set_position(&fb->tree->node, x, y); - } - - /* Resize: reallocate the pixman image if dimensions changed. */ - if (width > 0 && height > 0 && (width != fb->width || height != fb->height)) { - if (fb->image) - pixman_image_unref(fb->image); - fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, - width, height, NULL, 0); - fb->width = width; - fb->height = height; - fb->base.width = width; - fb->base.height = height; - /* Notify scene graph of new size — required for hit-testing. - * wlroots caches buffer dimensions; without this, clicks beyond - * the old size would miss this scene_buf. */ - if (fb->scene_buf) - wlr_scene_buffer_set_dest_size(fb->scene_buf, width, height); - } - - /* Update absolute position tracking — recursively for all descendants. */ - frame_buf_update_children_abs(win); -} - -static void wl_frame_paint_button(WCoreWindow *button, WTexture *texture, - unsigned long color, WPixmap *image, int pushed); - -/* - * wl_frame_paint — render the entire frame decoration into the pixman buffer. - * - * Uses wTextureRenderImage() (pure CPU) for titlebar/resizebar backgrounds, - * pixman for bevel lines, and the WINGs font backend for title text. - * Returns 1 (handled) so wFrameWindowPaint skips the Xlib code path. - */ -static int -wl_frame_paint(struct WFrameWindow *fwin) -{ - struct wl_frame_buf *fb = frame_buf_find(fwin->core->window); - if (!fb || !fb->image) - return 0; - - int state = fwin->flags.state; - int total_w = fb->width; - int total_h = fb->height; - /* Clear the buffer to transparent black. */ - pixman_color_t clear = {0, 0, 0, 0}; /* transparent */ - pixman_image_fill_rectangles(PIXMAN_OP_SRC, fb->image, &clear, - 1, &(pixman_rectangle16_t){0, 0, total_w, total_h}); - - /* --- Titlebar --- */ - if (fwin->titlebar && fwin->top_width > 0) { - struct wl_frame_buf *tb_fb = frame_buf_find(fwin->titlebar->window); - pixman_image_t *tb_img = tb_fb ? tb_fb->image : fb->image; - int tb_w = total_w; /* always full frame width */ - int tb_h = fwin->top_width; - - /* In Wayland we composite buttons into the titlebar buffer, so - * the titlebar must be full-width and positioned at the frame origin - * regardless of what updateTitlebar() configured for X11 layout. */ - if (tb_fb) { - tb_fb->rel_x = 0; - tb_fb->rel_y = 0; - tb_fb->x = fb->x; - tb_fb->y = fb->y; - if (tb_fb->scene_buf) - wlr_scene_node_set_position(&tb_fb->scene_buf->node, 0, 0); - } - - /* Ensure titlebar buffer is full width */ - if (tb_fb && tb_fb->width < tb_w) { - pixman_image_unref(tb_fb->image); - tb_fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, - tb_w, tb_h, NULL, 0); - tb_fb->width = tb_w; - tb_fb->height = tb_h; - tb_fb->base.width = tb_w; - tb_fb->base.height = tb_h; - tb_img = tb_fb->image; - /* Notify scene graph so hit-testing covers the new size. */ - if (tb_fb->scene_buf) - wlr_scene_buffer_set_dest_size(tb_fb->scene_buf, tb_w, tb_h); - } - WTexture *tex = fwin->title_texture[state]; - - /* Clear titlebar buffer */ - if (tb_fb) { - pixman_color_t clear = {0, 0, 0, 0}; - pixman_image_fill_rectangles(PIXMAN_OP_SRC, tb_img, &clear, - 1, &(pixman_rectangle16_t){0, 0, tb_w, tb_h}); - } - - /* Render texture to RImage. */ - RImage *img = wTextureRenderImage(tex, tb_w, tb_h, WREL_RAISED); - if (img) { - uint32_t *dst = pixman_image_get_data(tb_img); - int dst_stride = pixman_image_get_stride(tb_img) / 4; - int x, y; - if (img->format == RRGBAFormat) { - for (y = 0; y < tb_h && y < img->height; y++) { - unsigned char *src_row = img->data + y * img->width * 4; - uint32_t *dst_row = dst + y * dst_stride; - for (x = 0; x < tb_w && x < img->width; x++) { - unsigned char r = src_row[x*4+0]; - unsigned char g = src_row[x*4+1]; - unsigned char b = src_row[x*4+2]; - unsigned char a = src_row[x*4+3]; - dst_row[x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | - ((uint32_t)g << 8) | (uint32_t)b; - } - } - } else { - /* RGB format */ - for (y = 0; y < tb_h && y < img->height; y++) { - unsigned char *src_row = img->data + y * img->width * 3; - uint32_t *dst_row = dst + y * dst_stride; - for (x = 0; x < tb_w && x < img->width; x++) { - unsigned char r = src_row[x*3+0]; - unsigned char g = src_row[x*3+1]; - unsigned char b = src_row[x*3+2]; - dst_row[x] = 0xFF000000U | ((uint32_t)r << 16) | - ((uint32_t)g << 8) | (uint32_t)b; - } - } - } - RReleaseImage(img); - } - - /* Draw title text. */ - if (fwin->title && *fwin->font && fwin->title_color[state]) { - int lofs = 6, rofs = 6; - int titlelen, tw, tx, ty, th; - char *title; - - if (fwin->left_button && !fwin->flags.hide_left_button && - !fwin->flags.lbutton_dont_fit) - lofs += fwin->left_button->width + 3; - if (fwin->right_button && !fwin->flags.hide_right_button && - !fwin->flags.rbutton_dont_fit) - rofs += fwin->right_button->width + 3; - - title = ShrinkString(*fwin->font, fwin->title, tb_w - lofs - rofs); - titlelen = strlen(title); - tw = WMWidthOfString(*fwin->font, title, titlelen); - th = WMFontHeight(*fwin->font); - - switch (fwin->flags.justification) { - case WTJ_LEFT: tx = lofs; break; - case WTJ_RIGHT: tx = tb_w - tw - rofs; break; - default: tx = (tb_w - tw) / 2; break; - } - ty = (tb_h - th) / 2; - - WMDrawString(fwin->screen_ptr->wmscreen, (WMDrawable)tb_img, - fwin->title_color[state], *fwin->font, - tx, ty, title, titlelen); - wfree(title); - } - - /* Commit titlebar buffer */ - if (tb_fb && tb_fb->scene_buf) { - wlr_scene_buffer_set_buffer_with_damage(tb_fb->scene_buf, &tb_fb->base, NULL); - } - } - - /* --- Titlebar buttons --- */ - if (fwin->titlebar && fwin->top_width > 0) { - if (fwin->left_button && !fwin->flags.hide_left_button && - !fwin->flags.lbutton_dont_fit) - wl_frame_paint_button(fwin->left_button, - fwin->title_texture[state], - WMColorPixel(fwin->title_color[state]), - fwin->lbutton_image, False); - if (fwin->right_button && !fwin->flags.hide_right_button && - !fwin->flags.rbutton_dont_fit) - wl_frame_paint_button(fwin->right_button, - fwin->title_texture[state], - WMColorPixel(fwin->title_color[state]), - fwin->rbutton_image, False); - } - - /* --- Resizebar --- */ - /* Skip resizebar when frame is shaded (height ≤ titlebar). */ - bool show_resizebar = fwin->resizebar && fwin->bottom_width > 0 - && total_h > (int)fwin->top_width; - if (show_resizebar) { - int rb_y = total_h - fwin->bottom_width; - int rb_w = total_w; - int rb_h = fwin->bottom_width; - WTexture *tex = fwin->resizebar_texture[0]; - - RImage *img = wTextureRenderImage(tex, rb_w, rb_h, WREL_FLAT); - if (img) { - uint32_t *dst = pixman_image_get_data(fb->image); - int dst_stride = pixman_image_get_stride(fb->image) / 4; - int x, y; - if (img->format == RRGBAFormat) { - for (y = 0; y < rb_h && y < img->height; y++) { - unsigned char *src_row = img->data + y * img->width * 4; - uint32_t *dst_row = dst + (rb_y + y) * dst_stride; - for (x = 0; x < rb_w && x < img->width; x++) { - unsigned char r = src_row[x*4+0]; - unsigned char g = src_row[x*4+1]; - unsigned char b = src_row[x*4+2]; - unsigned char a = src_row[x*4+3]; - dst_row[x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | - ((uint32_t)g << 8) | (uint32_t)b; - } - } - } else { - for (y = 0; y < rb_h && y < img->height; y++) { - unsigned char *src_row = img->data + y * img->width * 3; - uint32_t *dst_row = dst + (rb_y + y) * dst_stride; - for (x = 0; x < rb_w && x < img->width; x++) { - unsigned char r = src_row[x*3+0]; - unsigned char g = src_row[x*3+1]; - unsigned char b = src_row[x*3+2]; - dst_row[x] = 0xFF000000U | ((uint32_t)r << 16) | - ((uint32_t)g << 8) | (uint32_t)b; - } - } - } - RReleaseImage(img); - } - - /* Draw resizebar corner grab indicators */ - int cw = fwin->resizebar_corner_width; - uint32_t *dst = pixman_image_get_data(fb->image); - int dst_stride = pixman_image_get_stride(fb->image) / 4; - int midx = (rb_w < 1) ? 0 : rb_w / 2; - int midy = (rb_h < 1) ? 0 : rb_h / 2; - uint32_t base_px = dst[(rb_y + midy) * dst_stride + midx]; - unsigned char br = (base_px >> 16) & 0xFF; - unsigned char bg = (base_px >> 8) & 0xFF; - unsigned char bb = (base_px) & 0xFF; - uint32_t dim_px = 0xFF000000U | - (((unsigned)((br > 40) ? (br - 40) : 0) << 16)) | - (((unsigned)((bg > 40) ? (bg - 40) : 0) << 8)) | - ((unsigned)((bb > 40) ? (bb - 40) : 0)); - uint32_t light_px = 0xFF000000U | - (((unsigned)((br < 175) ? (br + 80) : 255) << 16)) | - (((unsigned)((bg < 175) ? (bg + 80) : 255) << 8)) | - ((unsigned)((bb < 175) ? (bb + 80) : 255)); - - /* Top bevel: dim at y=0, light at y=1 */ - for (int x = 0; x < rb_w; x++) { - dst[(rb_y + 0) * dst_stride + x] = dim_px; - dst[(rb_y + 1) * dst_stride + x] = light_px; - } - - /* Left corner grab handle: vertical lines from y=2 to rb_h-1 */ - int xl = (cw < rb_w) ? cw : rb_w - 1; - if (xl > 0) { - for (int y = 2; y < rb_h; y++) { - dst[(rb_y + y) * dst_stride + xl - 1] = dim_px; - dst[(rb_y + y) * dst_stride + xl] = light_px; - } - } - - /* Right corner grab handle */ - int xr = (rb_w - cw - 2 >= 0) ? (rb_w - cw - 2) : 0; - if (xr + 1 < rb_w) { - for (int y = 2; y < rb_h; y++) { - dst[(rb_y + y) * dst_stride + xr] = dim_px; - dst[(rb_y + y) * dst_stride + xr + 1] = light_px; - } - } - } - - /* Hide the resizebar's own scene node when shaded. */ - if (fwin->resizebar) { - struct wl_frame_buf *rb_fb = frame_buf_find(fwin->resizebar->window); - if (rb_fb && rb_fb->scene_buf) - wlr_scene_node_set_enabled(&rb_fb->scene_buf->node, show_resizebar); - } - - /* Commit the buffer to the scene graph only if it has content (resizebar). */ - if (fb->scene_buf) { - if (show_resizebar) { - wlr_scene_node_set_enabled(&fb->scene_buf->node, true); - wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL); - } else { - wlr_scene_node_set_enabled(&fb->scene_buf->node, false); - } - } - - if (wl_state.output) - wlr_output_schedule_frame(wl_state.output); - - return 1; -} - /* ------------------------------------------------------------------ */ /* Focus */ /* ------------------------------------------------------------------ */ @@ -7238,7 +6534,7 @@ static void wl_noop_frame_alloc_border_pixel(unsigned long colormap, static void wl_noop_frame_update_texture(struct WFrameWindow *fwin) { (void)fwin; } -static void wl_frame_paint_button(WCoreWindow *button, WTexture *texture, +void wl_frame_paint_button(WCoreWindow *button, WTexture *texture, unsigned long color, WPixmap *image, int pushed) { (void)texture; (void)image; diff --git a/src/backend/wayland/wl_framebuf.c b/src/backend/wayland/wl_framebuf.c new file mode 100644 index 0000000..de9c716 --- /dev/null +++ b/src/backend/wayland/wl_framebuf.c @@ -0,0 +1,729 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../screen.h" +#include "../../framewin.h" +#include "../../texture.h" +#include "../../window.h" +#include "../../misc.h" +#include "wl_types.h" +#include + +/* Forward declarations from WINGs */ +extern void *W_RegisterBacking(unsigned long id, void *img); +extern void W_UnregisterBacking(unsigned long id); +extern void *W_GetViewBacking(unsigned long id); + +/* ------------------------------------------------------------------ */ + +void frame_buf_destroy(struct wlr_buffer *wlr_buf) +{ + struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base); + if (fb->image) + pixman_image_unref(fb->image); + wfree(fb); +} + +bool frame_buf_begin_data_ptr_access(struct wlr_buffer *wlr_buf, + uint32_t flags, void **data, uint32_t *format, size_t *stride) +{ + struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base); + (void)flags; + *data = pixman_image_get_data(fb->image); + *format = DRM_FORMAT_ARGB8888; + *stride = (size_t)pixman_image_get_stride(fb->image); + return true; +} + +void frame_buf_end_data_ptr_access(struct wlr_buffer *wlr_buf) +{ + (void)wlr_buf; +} + +const struct wlr_buffer_impl frame_buf_impl = { + .destroy = frame_buf_destroy, + .begin_data_ptr_access = frame_buf_begin_data_ptr_access, + .end_data_ptr_access = frame_buf_end_data_ptr_access, +}; + +struct wl_frame_buf * +frame_buf_find(WNativeWindow id) +{ + struct wl_frame_buf *fb; + wl_list_for_each(fb, &wl_state.frame_list, link) { + if (fb->id == id) return fb; + } + return NULL; +} + +/* + * fb_scene_buf_attach / fb_tree_attach + * + * Register destroy listeners on a scene_buf or tree so that fb->scene_buf + * (or fb->tree) is NULL'd when wlroots auto-destroys the node — typically + * via cascade when an ancestor scene_tree is destroyed. Without this, + * follow-up cleanup paths (wl_view_destroy_backing_impl, wl_frame_destroy) + * call wlr_scene_node_destroy on a freed node and segfault inside + * wl_list_remove against a zeroed list link. + */ +void fb_scene_buf_destroy_handler(struct wl_listener *l, void *d) +{ + (void)d; + struct wl_frame_buf *fb = wl_container_of(l, fb, scene_buf_destroy); + fb->scene_buf = NULL; + wl_list_remove(&fb->scene_buf_destroy.link); +} +void fb_tree_destroy_handler(struct wl_listener *l, void *d) +{ + (void)d; + struct wl_frame_buf *fb = wl_container_of(l, fb, tree_destroy); + fb->tree = NULL; + /* The tree's destroy cascades into scene_buf — its destroy handler + * will fire too and NULL fb->scene_buf separately. */ + wl_list_remove(&fb->tree_destroy.link); +} +void fb_attach_scene_buf_listener(struct wl_frame_buf *fb) +{ + if (!fb || !fb->scene_buf) return; + fb->scene_buf_destroy.notify = fb_scene_buf_destroy_handler; + wl_signal_add(&fb->scene_buf->node.events.destroy, + &fb->scene_buf_destroy); +} +void fb_attach_tree_listener(struct wl_frame_buf *fb) +{ + if (!fb || !fb->tree) return; + fb->tree_destroy.notify = fb_tree_destroy_handler; + wl_signal_add(&fb->tree->node.events.destroy, &fb->tree_destroy); +} + +/* Recursively update fb->x, fb->y for all descendants of `parent`, used + * to keep absolute-position tracking in sync after the parent moves. + * Hit-testing (frame_buf_at, frame_buf_button_at) and click coordinate + * translation depend on these fields being current. */ +void +frame_buf_update_children_abs(WNativeWindow parent_id) +{ + struct wl_frame_buf *parent = frame_buf_find(parent_id); + if (!parent) return; + struct wl_frame_buf *child; + wl_list_for_each(child, &wl_state.frame_list, link) { + if (child->parent_id == parent_id) { + child->x = parent->x + child->rel_x; + child->y = parent->y + child->rel_y; + /* Recurse into grandchildren */ + frame_buf_update_children_abs(child->id); + } + } + /* Also update toplevel views parented to this frame */ + struct wl_toplevel_view *cv; + wl_list_for_each(cv, &wl_state.toplevel_list, link) { + if (cv->frame_id == parent_id) { + cv->x = parent->x + cv->frame_rel_x; + cv->y = parent->y + cv->frame_rel_y; + } + } +} + +/* Hit-test: find the frame_buf under (px, py) that should receive the event. + * + * Returns the most-specific (innermost) frame_buf whose rendered pixels + * are topmost at (px, py), so the WM event layer receives the correct + * sub-window ID (titlebar, resizebar, button) needed to trigger move/resize. + * + * Scene-graph structure (level-tree model): + * + * scene->tree + * level_trees[N] <- level bucket + * fb->tree <- top-level frame grouping tree + * fb->scene_buf <- full-frame decoration pixels + * child_fb->scene_buf <- titlebar / resizebar sub-frames + * v->scene_node <- client surface (returns NULL) + * + * Strategy: + * 1. wlr_scene_node_at gives the topmost rendered node. + * 2. First check if that exact node is a child frame_buf's scene_buf + * (titlebar, resizebar, button) — if so, return it directly. + * 3. Walk up to the fb->tree level (parent is a level_tree bucket). + * 4. Match that window-root node to a top-level frame_buf's tree. + * 5. If it was a client surface (v->scene_node), return NULL. */ +struct wl_frame_buf * +frame_buf_at(int px, int py) +{ + if (!wl_state.scene) return NULL; + + double sx, sy; + struct wlr_scene_node *node = wlr_scene_node_at( + &wl_state.scene->tree.node, (double)px, (double)py, &sx, &sy); + if (!node) return NULL; + + struct wl_frame_buf *fb; + + /* Step 2: exact match — is the topmost node a child frame_buf's + * scene_buf (no tree wrapper)? This covers titlebars, resizebars, + * and button sub-frames, which are what the WM needs for move/resize. */ + wl_list_for_each(fb, &wl_state.frame_list, link) { + if (!fb->tree && fb->scene_buf && &fb->scene_buf->node == node) + return fb; + } + + /* Step 3: walk up until the parent is a level_tree bucket. + * The node we stop at is the fb->tree of a top-level frame, or + * a v->scene_node sitting directly in a level tree (unframed). */ + struct wlr_scene_node *n = node; + while (n->parent + && &n->parent->node != &wl_state.scene->tree.node) { + int _at_level = 0, _li; + for (_li = 0; _li < WL_NUM_LEVEL_BUCKETS; _li++) { + if (wl_state.level_trees[_li] && + n->parent == wl_state.level_trees[_li]) { + _at_level = 1; break; + } + } + if (_at_level) break; + n = &n->parent->node; + } + + /* Step 4: match window-root node to a top-level frame_buf. */ + wl_list_for_each(fb, &wl_state.frame_list, link) { + if (fb->tree && &fb->tree->node == n) + return fb; + } + + /* Step 5: match a tree-less frame_buf (WINGs dialog) whose scene_buf + * is a direct child of a level_tree bucket. */ + wl_list_for_each(fb, &wl_state.frame_list, link) { + if (!fb->tree && fb->scene_buf && &fb->scene_buf->node == n) + return fb; + } + + /* Step 6: topmost node was a client surface — not a WM frame. */ + return NULL; +} + +/* + * frame_buf_button_at — coordinate-based hit-test for virtual button frame_bufs. + * + * Buttons are composited into the titlebar buffer: their scene_buf nodes are + * in the scene graph but carry no committed buffer (scene_buf->buffer == NULL) + * and are therefore invisible to wlr_scene_node_at. We find them by walking + * the frame_list and comparing absolute screen coordinates. + * + * Returns the button fb whose absolute rect encloses (px,py) with the + * smallest area (most specific), or NULL if no button is under the pointer. + */ +struct wl_frame_buf * +frame_buf_button_at(int px, int py) +{ + struct wl_frame_buf *best = NULL; + struct wl_frame_buf *fb; + wl_list_for_each(fb, &wl_state.frame_list, link) { + if (!fb->parent_id) continue; /* top-level root */ + if (!fb->scene_buf) continue; /* no scene node */ + if (!fb->scene_buf->node.enabled) continue; /* hidden */ + /* Walk up the scene tree — if any ancestor is disabled, this + * frame_buf is not actually visible (just its node enabled flag + * is set, but a parent tree was disabled e.g. by window_unmap). */ + struct wlr_scene_node *n = fb->scene_buf->node.parent + ? &fb->scene_buf->node.parent->node : NULL; + int ancestor_disabled = 0; + while (n) { + if (!n->enabled) { ancestor_disabled = 1; break; } + n = n->parent ? &n->parent->node : NULL; + } + if (ancestor_disabled) continue; + /* Buttons are small children of the titlebar with enabled scene_bufs. + * Skip large nodes (titlebars, resizebars) by size threshold. */ + if (px < fb->x || px >= fb->x + fb->width) continue; + if (py < fb->y || py >= fb->y + fb->height) continue; + /* Prefer the smallest (most specific) enclosing fb. */ + if (!best || (fb->width * fb->height) < (best->width * best->height)) + best = fb; + } + return best; +} + +/* + * frame_buf_hit — resolve the WM sub-window at (px,py). + * + * Tries button hit-testing first (buttons are invisible to the scene graph + * because their pixels are composited into the titlebar buffer), then falls + * back to the scene-graph-based frame_buf_at for titlebars, resizebars, and + * top-level frames. + */ +struct wl_frame_buf * +frame_buf_hit(int px, int py) +{ + struct wl_frame_buf *fb = frame_buf_button_at(px, py); + return fb ? fb : frame_buf_at(px, py); +} + +/* ------------------------------------------------------------------ */ +/* Frame / window lifecycle */ +/* ------------------------------------------------------------------ */ + +WNativeWindow +wl_frame_create_toplevel(WScreen *scr, + int x, int y, int width, int height, + int border_width, + int depth, void *visual, + unsigned long colormap, + WMPixel border_pixel) +{ + (void)border_width; (void)depth; (void)visual; + (void)colormap; (void)border_pixel; (void)scr; + + if (width <= 0) width = 1; + if (height <= 0) height = 1; + + struct wl_frame_buf *fb = wmalloc(sizeof(*fb)); + memset(fb, 0, sizeof(*fb)); + + fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + width, height, NULL, 0); + if (!fb->image) { + wfree(fb); + return WNATIVE_NONE; + } + + wlr_buffer_init(&fb->base, &frame_buf_impl, width, height); + + fb->id = wl_state.next_synthetic_id++; + fb->x = x; + fb->y = y; + fb->width = width; + fb->height = height; + + /* Create scene buffer node under a per-window tree. + * The tree is positioned at (x,y) so all children use local offsets. + * Default to WL_LAYER_NORMAL; wl_stacking_set_level() will reparent + * to the correct bucket when AddToStackList() fires immediately after. */ + if (wl_state.scene) { + struct wlr_scene_tree *_parent = wl_state.level_trees[WL_LAYER_NORMAL] + ? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree; + fb->tree = wlr_scene_tree_create(_parent); + if (fb->tree) { + fb_attach_tree_listener(fb); + wlr_scene_node_set_position(&fb->tree->node, x, y); + fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base); + if (fb->scene_buf) { + fb_attach_scene_buf_listener(fb); + wlr_scene_node_set_position(&fb->scene_buf->node, 0, 0); + wlr_scene_node_set_enabled(&fb->scene_buf->node, false); + } + /* Tree starts disabled — wl_window_map enables it when the + * frame is logically mapped. Without this, decoration + * children (titlebar, buttons) of unmapped menus/dialogs + * appear as orphans because window_map_raised on those + * children enables their scene_bufs while the frame itself + * has not been mapped. */ + wlr_scene_node_set_enabled(&fb->tree->node, false); + } + } + + wl_list_insert(&wl_state.frame_list, &fb->link); + /* Register this frame_buf image in the WINGs backing table so + * WMDrawString() can resolve the window ID to a pixman_image_t + * (needed for miniwindow title drawing in update_icon_title). */ + W_RegisterBacking(fb->id, fb->image); + return fb->id; +} + +WNativeWindow +wl_frame_create_child(WNativeWindow parent_win, WScreen *scr, + int x, int y, int width, int height) +{ + (void)scr; + + if (width <= 0) width = 1; + if (height <= 0) height = 1; + + /* Compute absolute position from parent */ + int abs_x = x, abs_y = y; + struct wl_frame_buf *parent = frame_buf_find(parent_win); + if (parent) { + abs_x = parent->x + x; + abs_y = parent->y + y; + } + + struct wl_frame_buf *fb = wmalloc(sizeof(*fb)); + memset(fb, 0, sizeof(*fb)); + + fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + width, height, NULL, 0); + if (!fb->image) { + wfree(fb); + return WNATIVE_NONE; + } + + wlr_buffer_init(&fb->base, &frame_buf_impl, width, height); + + fb->id = wl_state.next_synthetic_id++; + fb->parent_id = parent_win; + fb->rel_x = x; + fb->rel_y = y; + fb->x = abs_x; + fb->y = abs_y; + fb->width = width; + fb->height = height; + + if (wl_state.scene) { + struct wlr_scene_tree *ptree = (parent && parent->tree) + ? parent->tree : &wl_state.scene->tree; + fb->scene_buf = wlr_scene_buffer_create(ptree, &fb->base); + if (fb->scene_buf) { + fb_attach_scene_buf_listener(fb); + wlr_scene_node_set_position(&fb->scene_buf->node, x, y); + wlr_scene_node_set_enabled(&fb->scene_buf->node, false); + } + } + + wl_list_insert(&wl_state.frame_list, &fb->link); + W_RegisterBacking(fb->id, fb->image); + return fb->id; +} + +void +wl_frame_destroy(WScreen *scr, WNativeWindow win) +{ + (void)scr; + struct wl_frame_buf *fb = frame_buf_find(win); + if (!fb) return; + W_UnregisterBacking(fb->id); + /* Destroy the scene_buf and (if any) the tree. Without destroying + * the tree, leaked tree nodes accumulate in the scene graph and + * can cause stale rendering or hit-testing issues (e.g. ghost + * titlebars after a menu is closed). */ + if (fb->scene_buf) + wlr_scene_node_destroy(&fb->scene_buf->node); + if (fb->tree) + wlr_scene_node_destroy(&fb->tree->node); + wl_list_remove(&fb->link); + wlr_buffer_drop(&fb->base); +} + +void +wl_frame_configure(WNativeWindow win, int x, int y, int width, int height) +{ + struct wl_frame_buf *fb = frame_buf_find(win); + if (!fb) return; + + /* Update position tracking */ + fb->rel_x = x; + fb->rel_y = y; + if (fb->parent_id) { + struct wl_frame_buf *parent = frame_buf_find(fb->parent_id); + if (parent) { + fb->x = parent->x + x; + fb->y = parent->y + y; + } else { + fb->x = x; + fb->y = y; + } + /* Child frame_buf: reposition scene_buf at relative offset within parent tree */ + if (fb->scene_buf) + wlr_scene_node_set_position(&fb->scene_buf->node, x, y); + } else { + fb->x = x; + fb->y = y; + /* Top-level frame: move the tree (children follow automatically) */ + if (fb->tree) + wlr_scene_node_set_position(&fb->tree->node, x, y); + } + + /* Resize: reallocate the pixman image if dimensions changed. */ + if (width > 0 && height > 0 && (width != fb->width || height != fb->height)) { + if (fb->image) + pixman_image_unref(fb->image); + fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + width, height, NULL, 0); + fb->width = width; + fb->height = height; + fb->base.width = width; + fb->base.height = height; + /* Notify scene graph of new size — required for hit-testing. + * wlroots caches buffer dimensions; without this, clicks beyond + * the old size would miss this scene_buf. */ + if (fb->scene_buf) + wlr_scene_buffer_set_dest_size(fb->scene_buf, width, height); + } + + /* Update absolute position tracking — recursively for all descendants. */ + frame_buf_update_children_abs(win); +} + +void wl_frame_paint_button(WCoreWindow *button, WTexture *texture, + unsigned long color, WPixmap *image, int pushed); + +/* + * wl_frame_paint — render the entire frame decoration into the pixman buffer. + * + * Uses wTextureRenderImage() (pure CPU) for titlebar/resizebar backgrounds, + * pixman for bevel lines, and the WINGs font backend for title text. + * Returns 1 (handled) so wFrameWindowPaint skips the Xlib code path. + */ +int +wl_frame_paint(struct WFrameWindow *fwin) +{ + struct wl_frame_buf *fb = frame_buf_find(fwin->core->window); + if (!fb || !fb->image) + return 0; + + int state = fwin->flags.state; + int total_w = fb->width; + int total_h = fb->height; + /* Clear the buffer to transparent black. */ + pixman_color_t clear = {0, 0, 0, 0}; /* transparent */ + pixman_image_fill_rectangles(PIXMAN_OP_SRC, fb->image, &clear, + 1, &(pixman_rectangle16_t){0, 0, total_w, total_h}); + + /* --- Titlebar --- */ + if (fwin->titlebar && fwin->top_width > 0) { + struct wl_frame_buf *tb_fb = frame_buf_find(fwin->titlebar->window); + pixman_image_t *tb_img = tb_fb ? tb_fb->image : fb->image; + int tb_w = total_w; /* always full frame width */ + int tb_h = fwin->top_width; + + /* In Wayland we composite buttons into the titlebar buffer, so + * the titlebar must be full-width and positioned at the frame origin + * regardless of what updateTitlebar() configured for X11 layout. */ + if (tb_fb) { + tb_fb->rel_x = 0; + tb_fb->rel_y = 0; + tb_fb->x = fb->x; + tb_fb->y = fb->y; + if (tb_fb->scene_buf) + wlr_scene_node_set_position(&tb_fb->scene_buf->node, 0, 0); + } + + /* Ensure titlebar buffer is full width */ + if (tb_fb && tb_fb->width < tb_w) { + pixman_image_unref(tb_fb->image); + tb_fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + tb_w, tb_h, NULL, 0); + tb_fb->width = tb_w; + tb_fb->height = tb_h; + tb_fb->base.width = tb_w; + tb_fb->base.height = tb_h; + tb_img = tb_fb->image; + /* Notify scene graph so hit-testing covers the new size. */ + if (tb_fb->scene_buf) + wlr_scene_buffer_set_dest_size(tb_fb->scene_buf, tb_w, tb_h); + } + WTexture *tex = fwin->title_texture[state]; + + /* Clear titlebar buffer */ + if (tb_fb) { + pixman_color_t clear = {0, 0, 0, 0}; + pixman_image_fill_rectangles(PIXMAN_OP_SRC, tb_img, &clear, + 1, &(pixman_rectangle16_t){0, 0, tb_w, tb_h}); + } + + /* Render texture to RImage. */ + RImage *img = wTextureRenderImage(tex, tb_w, tb_h, WREL_RAISED); + if (img) { + uint32_t *dst = pixman_image_get_data(tb_img); + int dst_stride = pixman_image_get_stride(tb_img) / 4; + int x, y; + if (img->format == RRGBAFormat) { + for (y = 0; y < tb_h && y < img->height; y++) { + unsigned char *src_row = img->data + y * img->width * 4; + uint32_t *dst_row = dst + y * dst_stride; + for (x = 0; x < tb_w && x < img->width; x++) { + unsigned char r = src_row[x*4+0]; + unsigned char g = src_row[x*4+1]; + unsigned char b = src_row[x*4+2]; + unsigned char a = src_row[x*4+3]; + dst_row[x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | + ((uint32_t)g << 8) | (uint32_t)b; + } + } + } else { + /* RGB format */ + for (y = 0; y < tb_h && y < img->height; y++) { + unsigned char *src_row = img->data + y * img->width * 3; + uint32_t *dst_row = dst + y * dst_stride; + for (x = 0; x < tb_w && x < img->width; x++) { + unsigned char r = src_row[x*3+0]; + unsigned char g = src_row[x*3+1]; + unsigned char b = src_row[x*3+2]; + dst_row[x] = 0xFF000000U | ((uint32_t)r << 16) | + ((uint32_t)g << 8) | (uint32_t)b; + } + } + } + RReleaseImage(img); + } + + /* Draw title text. */ + if (fwin->title && *fwin->font && fwin->title_color[state]) { + int lofs = 6, rofs = 6; + int titlelen, tw, tx, ty, th; + char *title; + + if (fwin->left_button && !fwin->flags.hide_left_button && + !fwin->flags.lbutton_dont_fit) + lofs += fwin->left_button->width + 3; + if (fwin->right_button && !fwin->flags.hide_right_button && + !fwin->flags.rbutton_dont_fit) + rofs += fwin->right_button->width + 3; + + title = ShrinkString(*fwin->font, fwin->title, tb_w - lofs - rofs); + titlelen = strlen(title); + tw = WMWidthOfString(*fwin->font, title, titlelen); + th = WMFontHeight(*fwin->font); + + switch (fwin->flags.justification) { + case WTJ_LEFT: tx = lofs; break; + case WTJ_RIGHT: tx = tb_w - tw - rofs; break; + default: tx = (tb_w - tw) / 2; break; + } + ty = (tb_h - th) / 2; + + WMDrawString(fwin->screen_ptr->wmscreen, (WMDrawable)tb_img, + fwin->title_color[state], *fwin->font, + tx, ty, title, titlelen); + wfree(title); + } + + /* Commit titlebar buffer */ + if (tb_fb && tb_fb->scene_buf) { + wlr_scene_buffer_set_buffer_with_damage(tb_fb->scene_buf, &tb_fb->base, NULL); + } + } + + /* --- Titlebar buttons --- */ + if (fwin->titlebar && fwin->top_width > 0) { + if (fwin->left_button && !fwin->flags.hide_left_button && + !fwin->flags.lbutton_dont_fit) + wl_frame_paint_button(fwin->left_button, + fwin->title_texture[state], + WMColorPixel(fwin->title_color[state]), + fwin->lbutton_image, False); + if (fwin->right_button && !fwin->flags.hide_right_button && + !fwin->flags.rbutton_dont_fit) + wl_frame_paint_button(fwin->right_button, + fwin->title_texture[state], + WMColorPixel(fwin->title_color[state]), + fwin->rbutton_image, False); + } + + /* --- Resizebar --- */ + /* Skip resizebar when frame is shaded (height ≤ titlebar). */ + bool show_resizebar = fwin->resizebar && fwin->bottom_width > 0 + && total_h > (int)fwin->top_width; + if (show_resizebar) { + int rb_y = total_h - fwin->bottom_width; + int rb_w = total_w; + int rb_h = fwin->bottom_width; + WTexture *tex = fwin->resizebar_texture[0]; + + RImage *img = wTextureRenderImage(tex, rb_w, rb_h, WREL_FLAT); + if (img) { + uint32_t *dst = pixman_image_get_data(fb->image); + int dst_stride = pixman_image_get_stride(fb->image) / 4; + int x, y; + if (img->format == RRGBAFormat) { + for (y = 0; y < rb_h && y < img->height; y++) { + unsigned char *src_row = img->data + y * img->width * 4; + uint32_t *dst_row = dst + (rb_y + y) * dst_stride; + for (x = 0; x < rb_w && x < img->width; x++) { + unsigned char r = src_row[x*4+0]; + unsigned char g = src_row[x*4+1]; + unsigned char b = src_row[x*4+2]; + unsigned char a = src_row[x*4+3]; + dst_row[x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | + ((uint32_t)g << 8) | (uint32_t)b; + } + } + } else { + for (y = 0; y < rb_h && y < img->height; y++) { + unsigned char *src_row = img->data + y * img->width * 3; + uint32_t *dst_row = dst + (rb_y + y) * dst_stride; + for (x = 0; x < rb_w && x < img->width; x++) { + unsigned char r = src_row[x*3+0]; + unsigned char g = src_row[x*3+1]; + unsigned char b = src_row[x*3+2]; + dst_row[x] = 0xFF000000U | ((uint32_t)r << 16) | + ((uint32_t)g << 8) | (uint32_t)b; + } + } + } + RReleaseImage(img); + } + + /* Draw resizebar corner grab indicators */ + int cw = fwin->resizebar_corner_width; + uint32_t *dst = pixman_image_get_data(fb->image); + int dst_stride = pixman_image_get_stride(fb->image) / 4; + int midx = (rb_w < 1) ? 0 : rb_w / 2; + int midy = (rb_h < 1) ? 0 : rb_h / 2; + uint32_t base_px = dst[(rb_y + midy) * dst_stride + midx]; + unsigned char br = (base_px >> 16) & 0xFF; + unsigned char bg = (base_px >> 8) & 0xFF; + unsigned char bb = (base_px) & 0xFF; + uint32_t dim_px = 0xFF000000U | + (((unsigned)((br > 40) ? (br - 40) : 0) << 16)) | + (((unsigned)((bg > 40) ? (bg - 40) : 0) << 8)) | + ((unsigned)((bb > 40) ? (bb - 40) : 0)); + uint32_t light_px = 0xFF000000U | + (((unsigned)((br < 175) ? (br + 80) : 255) << 16)) | + (((unsigned)((bg < 175) ? (bg + 80) : 255) << 8)) | + ((unsigned)((bb < 175) ? (bb + 80) : 255)); + + /* Top bevel: dim at y=0, light at y=1 */ + for (int x = 0; x < rb_w; x++) { + dst[(rb_y + 0) * dst_stride + x] = dim_px; + dst[(rb_y + 1) * dst_stride + x] = light_px; + } + + /* Left corner grab handle: vertical lines from y=2 to rb_h-1 */ + int xl = (cw < rb_w) ? cw : rb_w - 1; + if (xl > 0) { + for (int y = 2; y < rb_h; y++) { + dst[(rb_y + y) * dst_stride + xl - 1] = dim_px; + dst[(rb_y + y) * dst_stride + xl] = light_px; + } + } + + /* Right corner grab handle */ + int xr = (rb_w - cw - 2 >= 0) ? (rb_w - cw - 2) : 0; + if (xr + 1 < rb_w) { + for (int y = 2; y < rb_h; y++) { + dst[(rb_y + y) * dst_stride + xr] = dim_px; + dst[(rb_y + y) * dst_stride + xr + 1] = light_px; + } + } + } + + /* Hide the resizebar's own scene node when shaded. */ + if (fwin->resizebar) { + struct wl_frame_buf *rb_fb = frame_buf_find(fwin->resizebar->window); + if (rb_fb && rb_fb->scene_buf) + wlr_scene_node_set_enabled(&rb_fb->scene_buf->node, show_resizebar); + } + + /* Commit the buffer to the scene graph only if it has content (resizebar). */ + if (fb->scene_buf) { + if (show_resizebar) { + wlr_scene_node_set_enabled(&fb->scene_buf->node, true); + wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL); + } else { + wlr_scene_node_set_enabled(&fb->scene_buf->node, false); + } + } + + if (wl_state.output) + wlr_output_schedule_frame(wl_state.output); + + return 1; +} + diff --git a/src/backend/wayland/wl_types.h b/src/backend/wayland/wl_types.h index 1c4a24f..547ce25 100644 --- a/src/backend/wayland/wl_types.h +++ b/src/backend/wayland/wl_types.h @@ -483,3 +483,18 @@ void wl_icccm_set_class_hint_if_absent(WNativeWindow win, const char *wm_class, void wl_icccm_set_command(WNativeWindow win, char **argv, int argc); int wl_icccm_get_command(WNativeWindow win, char ***argv_out, int *argc_out); void wl_icccm_free_command(char **argv); + +/* Frame buffer module declarations (extracted to wl_framebuf.c) */ +struct wl_frame_buf *frame_buf_find(WNativeWindow id); +struct wl_frame_buf *frame_buf_at(int px, int py); +struct wl_frame_buf *frame_buf_button_at(int px, int py); +struct wl_frame_buf *frame_buf_hit(int px, int py); +WNativeWindow wl_frame_create_toplevel(WScreen *scr, int x, int y, int width, int height, int border_width, int depth, void *visual, unsigned long colormap, WMPixel border_pixel); +WNativeWindow wl_frame_create_child(WNativeWindow parent_win, WScreen *scr, int x, int y, int width, int height); +void wl_frame_destroy(WScreen *scr, WNativeWindow win); +void wl_frame_configure(WNativeWindow win, int x, int y, int width, int height); +int wl_frame_paint(struct WFrameWindow *fwin); +extern const struct wlr_buffer_impl frame_buf_impl; +void fb_attach_tree_listener(struct wl_frame_buf *fb); +void fb_attach_scene_buf_listener(struct wl_frame_buf *fb); +void wl_frame_paint_button(WCoreWindow *button, WTexture *texture, unsigned long color, WPixmap *image, int pushed);