1114 lines
41 KiB
C
1114 lines
41 KiB
C
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <wayland-server-core.h>
|
|
#include <wlr/types/wlr_scene.h>
|
|
#include <wlr/types/wlr_buffer.h>
|
|
#include <wlr/interfaces/wlr_buffer.h>
|
|
#include <wlr/types/wlr_output.h>
|
|
#include <libdrm/drm_fourcc.h>
|
|
#include <pixman.h>
|
|
#include "../../def_pixmaps.h"
|
|
#include "../../screen.h"
|
|
#include "../../framewin.h"
|
|
#include "../../texture.h"
|
|
#include "../../window.h"
|
|
#include "../../misc.h"
|
|
#include "wl_types.h"
|
|
#include <WINGs/WINGsP.h>
|
|
|
|
/* 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);
|
|
/* Unlink destroy listeners before freeing so cascade signals
|
|
* from parent tree destruction can't reach this freed struct. */
|
|
if (fb->tree_destroy.link.next)
|
|
wl_list_remove(&fb->tree_destroy.link);
|
|
if (fb->scene_buf_destroy.link.next)
|
|
wl_list_remove(&fb->scene_buf_destroy.link);
|
|
if (fb->bg_pixmap)
|
|
pixman_image_unref(fb->bg_pixmap);
|
|
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;
|
|
}
|
|
|
|
/* Resolve a frame_buf ID to its pixman image (for WMDrawString). */
|
|
void *W_GetFrameBufImage(unsigned long id)
|
|
{
|
|
struct wl_frame_buf *fb = frame_buf_find((WNativeWindow)id);
|
|
return fb ? fb->image : NULL;
|
|
}
|
|
|
|
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 7: 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.
|
|
*/
|
|
void
|
|
wl_framebuf_refresh_all_positions(void)
|
|
{
|
|
struct wl_frame_buf *fb;
|
|
wl_list_for_each(fb, &wl_state.frame_list, link) {
|
|
if (fb->parent_id)
|
|
continue;
|
|
if (fb->tree)
|
|
wlr_scene_node_set_position(&fb->tree->node, fb->x, fb->y);
|
|
else if (fb->scene_buf)
|
|
wlr_scene_node_set_position(&fb->scene_buf->node, fb->x, fb->y);
|
|
frame_buf_update_children_abs(fb->id);
|
|
}
|
|
}
|
|
|
|
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 void
|
|
wl_dump_hit_debug(int px, int py, struct wl_frame_buf *fb)
|
|
{
|
|
if (!fb)
|
|
return;
|
|
wwarning("hit: ptr=(%d,%d) fb=0x%lx abs=(%d,%d) rel=(%d,%d) size=(%d,%d) parent=0x%lx",
|
|
px, py, (unsigned long)fb->id, fb->x, fb->y, fb->rel_x, fb->rel_y,
|
|
fb->width, fb->height, (unsigned long)fb->parent_id);
|
|
if (fb->parent_id) {
|
|
struct wl_frame_buf *parent = frame_buf_find(fb->parent_id);
|
|
if (parent) {
|
|
wwarning("hit: parent fb=0x%lx abs=(%d,%d) size=(%d,%d)",
|
|
(unsigned long)parent->id, parent->x, parent->y,
|
|
parent->width, parent->height);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct wl_frame_buf *
|
|
frame_buf_hit(int px, int py)
|
|
{
|
|
struct wl_frame_buf *fb = frame_buf_at(px, py);
|
|
if (!fb)
|
|
fb = frame_buf_button_at(px, py);
|
|
|
|
/* Walk children (and grandchildren) to find the most specific
|
|
* frame_buf enclosing the point. Handles titlebar/resizebar buttons
|
|
* AND WINGs views nested inside fake_leader frames. */
|
|
if (fb) {
|
|
int depth;
|
|
for (depth = 0; depth < 4; depth++) {
|
|
struct wl_frame_buf *child, *best_child = NULL;
|
|
wl_list_for_each(child, &wl_state.frame_list, link) {
|
|
if (child->parent_id != fb->id) continue;
|
|
if (px < child->x || px >= child->x + child->width) continue;
|
|
if (py < child->y || py >= child->y + child->height) continue;
|
|
if (!best_child ||
|
|
(child->width * child->height) < (best_child->width * best_child->height))
|
|
best_child = child;
|
|
}
|
|
if (best_child)
|
|
fb = best_child;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
return fb;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
fb->canary = 0xDEADBEEF;
|
|
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);
|
|
}
|
|
}
|
|
|
|
fb->canary = 0xDEADBEEF;
|
|
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;
|
|
|
|
/* Destroy child frame_bufs whose parent is this one */
|
|
struct wl_frame_buf *child, *tmp;
|
|
wl_list_for_each_safe(child, tmp, &wl_state.frame_list, link) {
|
|
if (child->parent_id == win) {
|
|
if (child->scene_buf)
|
|
wlr_scene_node_destroy(&child->scene_buf->node);
|
|
if (child->tree)
|
|
wlr_scene_node_destroy(&child->tree->node);
|
|
wl_list_remove(&child->link);
|
|
wlr_buffer_drop(&child->base);
|
|
}
|
|
}
|
|
|
|
/* Before destroying the tree, detach any other frame_bufs whose
|
|
* trees are descendants of ours (e.g. reparented fake_leaders) so
|
|
* their tree_destroy_handler doesn't fire on freed memory. */
|
|
if (fb->tree) {
|
|
struct wl_frame_buf *other, *otmp;
|
|
wl_list_for_each_safe(other, otmp, &wl_state.frame_list, link) {
|
|
if (other == fb || !other->tree) continue;
|
|
struct wlr_scene_tree *p = other->tree->node.parent;
|
|
while (p) {
|
|
if (p == fb->tree) {
|
|
wl_list_remove(&other->tree_destroy.link);
|
|
wl_list_init(&other->tree_destroy.link);
|
|
other->tree = NULL;
|
|
break;
|
|
}
|
|
p = p->node.parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
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->scene_buf)
|
|
wlr_scene_buffer_set_buffer(fb->scene_buf, NULL);
|
|
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;
|
|
W_RegisterBacking(fb->id, fb->image);
|
|
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.
|
|
*/
|
|
static void
|
|
frame_buf_check_canaries(void)
|
|
{
|
|
struct wl_frame_buf *fb;
|
|
wl_list_for_each(fb, &wl_state.frame_list, link) {
|
|
if (fb->canary != 0xDEADBEEF) {
|
|
{
|
|
FILE *f = fopen("/tmp/wm-canary.log", "w");
|
|
if (f) {
|
|
fprintf(f, "CANARY TRASHED: fb id=0x%lx size=%dx%d parent=0x%lx canary=0x%08x\n",
|
|
(unsigned long)fb->id, fb->width, fb->height,
|
|
(unsigned long)fb->parent_id, fb->canary);
|
|
struct wl_frame_buf *f2;
|
|
wl_list_for_each(f2, &wl_state.frame_list, link) {
|
|
fprintf(f, " fb %p id=0x%lx %dx%d parent=0x%lx canary=%s\n",
|
|
(void*)f2, (unsigned long)f2->id, f2->width, f2->height,
|
|
(unsigned long)f2->parent_id,
|
|
f2->canary == 0xDEADBEEF ? "OK" : "BAD");
|
|
}
|
|
fclose(f);
|
|
}
|
|
}
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
wl_frame_paint(struct WFrameWindow *fwin)
|
|
{
|
|
frame_buf_check_canaries();
|
|
/* During live resize, skip expensive repaint — decorations content
|
|
* doesn't change, only geometry. Paint once on grab release. */
|
|
if (wl_state.grab_type == 2)
|
|
return 0;
|
|
|
|
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;
|
|
int tb_in_parent = 0;
|
|
/* 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;
|
|
if (tb_h > total_h)
|
|
tb_h = total_h;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* If titlebar buffer is too small, draw directly into the
|
|
* parent frame buffer to avoid buffer overflow. */
|
|
if (tb_fb && (tb_fb->width < tb_w || tb_fb->height < tb_h)) {
|
|
tb_img = fb->image;
|
|
tb_fb = NULL;
|
|
tb_in_parent = 1;
|
|
}
|
|
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 dst_h = pixman_image_get_height(tb_img);
|
|
if (tb_h > dst_h || tb_w > dst_stride) {
|
|
wwarning("OVERFLOW: tb_w=%d tb_h=%d dst_stride=%d dst_h=%d fb=%dx%d tb_fb=%p",
|
|
tb_w, tb_h, dst_stride, dst_h, total_w, total_h, (void*)tb_fb);
|
|
RReleaseImage(img);
|
|
goto skip_titlebar;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
skip_titlebar:
|
|
/* --- 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 parent frame buffer when it has visible content:
|
|
* resizebar pixels (unshaded) or titlebar fallback (when the titlebar
|
|
* child buffer was too small and we drew into the parent instead). */
|
|
if (fb->scene_buf) {
|
|
if (show_resizebar || tb_in_parent) {
|
|
wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL);
|
|
wlr_scene_node_set_enabled(&fb->scene_buf->node, true);
|
|
} else {
|
|
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
|
|
}
|
|
}
|
|
|
|
if (wl_state.output)
|
|
wlr_output_schedule_frame(wl_state.output);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Render XPM character data into an alpha mask RImage.
|
|
* Walks the XPM rows directly — only needs the character grid,
|
|
* not the XPM color definitions (just "None" detection). */
|
|
static RImage *xpm_to_alpha_mask(char **xpm)
|
|
{
|
|
int w = 0, h = 0, ncolors = 0, cpp = 0;
|
|
if (sscanf(xpm[0], "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4)
|
|
return NULL;
|
|
if (w <= 0 || h <= 0 || ncolors < 0 || (cpp != 1 && cpp != 2))
|
|
return NULL;
|
|
char none_sym[3] = {0, 0, 0};
|
|
for (int _i = 1; _i <= ncolors; _i++) {
|
|
const char *def = xpm[_i];
|
|
if (strstr(def, "None") || strstr(def, "none")) {
|
|
none_sym[0] = def[0];
|
|
if (cpp == 2 && def[1] != ' ') none_sym[1] = def[1];
|
|
break;
|
|
}
|
|
}
|
|
RImage *img = RCreateImage(w, h, True);
|
|
if (!img) return NULL;
|
|
memset(img->data, 0, w * h * 4);
|
|
int data_start = 1 + ncolors;
|
|
for (int _r = 0; _r < h && (data_start + _r) < 512; _r++) {
|
|
const char *row = xpm[data_start + _r];
|
|
int rowlen = strlen(row);
|
|
for (int _c = 0; _c < w && _c < rowlen; _c++) {
|
|
int is_none = (none_sym[0] && row[_c] == none_sym[0]);
|
|
if (cpp == 2 && _c + 1 < rowlen && none_sym[1])
|
|
is_none = is_none && (row[_c + 1] == none_sym[1]);
|
|
if (!is_none)
|
|
img->data[(_r * w + _c) * 4 + 3] = 0xFF;
|
|
}
|
|
}
|
|
return img;
|
|
}
|
|
|
|
void
|
|
wl_frame_paint_button(WCoreWindow *button, WTexture *texture,
|
|
unsigned long color, WPixmap *image, int pushed)
|
|
{
|
|
(void)texture; (void)image;
|
|
WFrameWindow *fwin = (WFrameWindow *)button->descriptor.parent;
|
|
if (!fwin || !fwin->core) {
|
|
return;
|
|
}
|
|
struct wl_frame_buf *btn_fb = frame_buf_find(button->window);
|
|
if (!btn_fb || !btn_fb->image) {
|
|
return;
|
|
}
|
|
|
|
int bw = btn_fb->width;
|
|
int bh = btn_fb->height;
|
|
if (bw < 3 || bh < 3)
|
|
return;
|
|
|
|
/* Build button icon RImages from XPM data arrays directly */
|
|
if (!wl_state.btn_close_img)
|
|
wl_state.btn_close_img = xpm_to_alpha_mask(PRED_CLOSE_XPM);
|
|
if (!wl_state.btn_iconify_img)
|
|
wl_state.btn_iconify_img = xpm_to_alpha_mask(PRED_ICONIFY_XPM);
|
|
|
|
RImage *icon = NULL;
|
|
if (button == fwin->right_button)
|
|
icon = wl_state.btn_close_img;
|
|
else if (button == fwin->left_button)
|
|
icon = wl_state.btn_iconify_img;
|
|
if (!icon)
|
|
return;
|
|
|
|
struct wl_frame_buf *parent_fb = frame_buf_find(fwin->core->window);
|
|
struct wl_frame_buf *tb_fb = fwin->titlebar ? frame_buf_find(fwin->titlebar->window) : NULL;
|
|
uint32_t bg_pixel = 0xFF333333;
|
|
/* Sample button background from the titlebar buffer, or from the
|
|
* parent frame buffer if the titlebar was rendered there (fallback). */
|
|
if (tb_fb && tb_fb->image && tb_fb->width >= btn_fb->rel_x + bw) {
|
|
uint32_t *tb_data = pixman_image_get_data(tb_fb->image);
|
|
int tb_stride = pixman_image_get_stride(tb_fb->image) / 4;
|
|
int sample_y = btn_fb->rel_y + bh / 2;
|
|
int sample_x = btn_fb->rel_x + bw / 2;
|
|
if (sample_y >= 0 && sample_y < tb_fb->height &&
|
|
sample_x >= 0 && sample_x < tb_fb->width)
|
|
bg_pixel = tb_data[sample_y * tb_stride + sample_x];
|
|
} else if (parent_fb && parent_fb->image) {
|
|
uint32_t *p_data = pixman_image_get_data(parent_fb->image);
|
|
int p_stride = pixman_image_get_stride(parent_fb->image) / 4;
|
|
int sample_y = btn_fb->rel_y + bh / 2;
|
|
int sample_x = btn_fb->rel_x + bw / 2;
|
|
if (sample_y >= 0 && sample_y < parent_fb->height &&
|
|
sample_x >= 0 && sample_x < parent_fb->width)
|
|
bg_pixel = p_data[sample_y * p_stride + sample_x];
|
|
}
|
|
|
|
uint32_t fg_pixel = 0xFF000000 | ((color >> 16 & 0xFF) << 16) |
|
|
((color >> 8 & 0xFF) << 8) | (color & 0xFF);
|
|
|
|
uint32_t fill_fg = pushed ? bg_pixel : fg_pixel;
|
|
|
|
uint32_t *dst = pixman_image_get_data(btn_fb->image);
|
|
int dst_stride = pixman_image_get_stride(btn_fb->image) / 4;
|
|
|
|
/* Render button background with same texture+bevel as titlebar */
|
|
RImage *bg_img = wTextureRenderImage(texture, bw, bh,
|
|
pushed ? WREL_SUNKEN : WREL_RAISED);
|
|
if (bg_img) {
|
|
unsigned char *src = bg_img->data;
|
|
int has_alpha = (bg_img->format == RRGBAFormat);
|
|
for (int y = 0; y < bh && y < bg_img->height; y++) {
|
|
for (int x = 0; x < bw && x < bg_img->width; x++) {
|
|
int si = (y * bg_img->width + x) * (has_alpha ? 4 : 3);
|
|
unsigned char r = src[si], g = src[si+1], b = src[si+2];
|
|
unsigned char a = has_alpha ? src[si+3] : 0xFF;
|
|
dst[y * dst_stride + x] = ((uint32_t)a << 24) |
|
|
((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
|
|
}
|
|
}
|
|
RReleaseImage(bg_img);
|
|
} else {
|
|
uint32_t fill_bg = pushed ? fg_pixel : bg_pixel;
|
|
for (int y = 0; y < bh; y++)
|
|
for (int x = 0; x < bw; x++)
|
|
dst[y * dst_stride + x] = fill_bg;
|
|
}
|
|
|
|
int ix = (bw - icon->width) / 2 + (pushed ? 1 : 0);
|
|
int iy = (bh - icon->height) / 2 + (pushed ? 1 : 0);
|
|
|
|
for (int y = 0; y < icon->height; y++) {
|
|
int dy = iy + y;
|
|
if (dy < 0 || dy >= bh) continue;
|
|
uint32_t *dst_row = dst + dy * dst_stride;
|
|
for (int x = 0; x < icon->width; x++) {
|
|
int dx = ix + x;
|
|
if (dx < 0 || dx >= bw) continue;
|
|
unsigned char a;
|
|
if (icon->format == RRGBAFormat)
|
|
a = icon->data[(y * icon->width + x) * 4 + 3];
|
|
else
|
|
a = 255;
|
|
if (a == 0) continue;
|
|
dst_row[dx] = fill_fg;
|
|
}
|
|
}
|
|
|
|
wlr_scene_buffer_set_buffer_with_damage(btn_fb->scene_buf, &btn_fb->base, NULL);
|
|
wlr_scene_node_set_enabled(&btn_fb->scene_buf->node, true);
|
|
wlr_scene_node_raise_to_top(&btn_fb->scene_buf->node);
|
|
}
|
|
|
|
void
|
|
wl_netwm_update_opacity(WWindow *wwin)
|
|
{
|
|
if (!wwin->frame)
|
|
return;
|
|
|
|
struct wl_frame_buf *fb = frame_buf_find(wwin->frame->core->window);
|
|
if (!fb || !fb->scene_buf)
|
|
return;
|
|
|
|
float alpha = 1.0f;
|
|
if (wwin->client_win)
|
|
alpha = wl_xwayland_get_window_opacity(wwin->client_win);
|
|
|
|
wlr_scene_buffer_set_opacity(fb->scene_buf, alpha);
|
|
|
|
struct wl_frame_buf *child;
|
|
wl_list_for_each(child, &wl_state.frame_list, link) {
|
|
if (child->parent_id == fb->id && child->scene_buf)
|
|
wlr_scene_buffer_set_opacity(child->scene_buf, alpha);
|
|
}
|
|
|
|
struct wl_toplevel_view *v;
|
|
wl_list_for_each(v, &wl_state.toplevel_list, link) {
|
|
if (v->frame_id == fb->id && v->scene_node) {
|
|
struct wlr_scene_node *node;
|
|
wl_list_for_each(node, &v->scene_node->children, link) {
|
|
if (node->type == WLR_SCENE_NODE_BUFFER)
|
|
wlr_scene_buffer_set_opacity(
|
|
wlr_scene_buffer_from_node(node), alpha);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void wl_frame_paint_resizebar(WFrameWindow *fwin)
|
|
{
|
|
/* Resizebar is rendered into the parent frame buffer by wl_frame_paint.
|
|
* Don't commit a separate buffer here — it would overlay the texture. */
|
|
(void)fwin;
|
|
}
|
|
|
|
void wl_frame_paint_titlebar(WFrameWindow *fwin, int state)
|
|
{
|
|
if (!fwin->titlebar || !fwin->titlebar->window)
|
|
return;
|
|
struct wl_frame_buf *fb = frame_buf_find((WNativeWindow)fwin->titlebar->window);
|
|
if (!fb || !fb->image)
|
|
return;
|
|
|
|
WScreen *scr = fwin->screen_ptr;
|
|
uint32_t *dst = (uint32_t *)pixman_image_get_data(fb->image);
|
|
int stride = pixman_image_get_stride(fb->image) / 4;
|
|
|
|
/* Background: use texture color or default */
|
|
uint32_t bg;
|
|
if (fwin->title_texture[state] &&
|
|
fwin->title_texture[state]->any.type == WTEX_SOLID) {
|
|
unsigned long px = fwin->title_texture[state]->solid.normal.pixel;
|
|
bg = 0xFF000000 | (px & 0xFFFFFF);
|
|
} else {
|
|
bg = (state == 0) ? 0xFF4A6984 : 0xFF808080; /* focused blue / unfocused grey */
|
|
}
|
|
|
|
for (int row = 0; row < fb->height; row++)
|
|
for (int col = 0; col < fb->width; col++)
|
|
dst[row * stride + col] = bg;
|
|
|
|
/* Draw title text */
|
|
if (fwin->title && fwin->font && fwin->title_color[state]) {
|
|
char *title = ShrinkString(*fwin->font, fwin->title, fb->width - 12);
|
|
int titlelen = strlen(title);
|
|
int tw = WMWidthOfString(*fwin->font, title, titlelen);
|
|
int x;
|
|
switch (fwin->flags.justification) {
|
|
case WTJ_LEFT: x = 6; break;
|
|
case WTJ_RIGHT: x = fb->width - tw - 6; break;
|
|
default: x = (fb->width - tw) / 2; break;
|
|
}
|
|
int fh = WMFontHeight(*fwin->font);
|
|
int y = (fb->height - fh) / 2;
|
|
|
|
WMDrawable d = (WMDrawable)(uintptr_t)fb->image;
|
|
WMDrawString(scr->wmscreen, d, fwin->title_color[state],
|
|
*fwin->font, x, y, title, titlelen);
|
|
wfree(title);
|
|
}
|
|
|
|
wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL);
|
|
}
|
|
|