967 lines
36 KiB
C
967 lines
36 KiB
C
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
#include <string.h>
|
|
#include <stdlib.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 <X11/Xlib.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);
|
|
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;
|
|
}
|
|
|
|
/* 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) {
|
|
wwarning("btn: no fwin");
|
|
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 *tb_fb = fwin->titlebar ? frame_buf_find(fwin->titlebar->window) : NULL;
|
|
uint32_t bg_pixel = 0xFF333333;
|
|
if (tb_fb && tb_fb->image) {
|
|
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];
|
|
}
|
|
|
|
uint32_t fg_pixel = 0xFF000000 | ((color >> 16 & 0xFF) << 16) |
|
|
((color >> 8 & 0xFF) << 8) | (color & 0xFF);
|
|
uint32_t fill_bg = pushed ? fg_pixel : bg_pixel;
|
|
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;
|
|
|
|
for (int y = 0; y < bh; y++)
|
|
for (int x = 0; x < bw; x++)
|
|
dst[y * dst_stride + x] = fill_bg;
|
|
|
|
unsigned char bg_r = (bg_pixel >> 16) & 0xFF;
|
|
unsigned char bg_g = (bg_pixel >> 8) & 0xFF;
|
|
unsigned char bg_b = bg_pixel & 0xFF;
|
|
uint32_t hi_c = 0xFF000000 | ((uint32_t)(bg_r + (255-bg_r)/3) << 16) |
|
|
((uint32_t)(bg_g + (255-bg_g)/3) << 8) | (bg_b + (255-bg_b)/3);
|
|
uint32_t lo_c = 0xFF000000 | ((uint32_t)(bg_r * 2/3) << 16) |
|
|
((uint32_t)(bg_g * 2/3) << 8) | (bg_b * 2/3);
|
|
uint32_t hi = pushed ? lo_c : hi_c;
|
|
uint32_t lo = pushed ? hi_c : lo_c;
|
|
for (int x = 0; x < bw; x++) { dst[x] = hi; dst[(bh-1) * dst_stride + x] = lo; }
|
|
for (int y = 0; y < bh; y++) { dst[y * dst_stride] = hi; dst[y * dst_stride + bw - 1] = lo; }
|
|
|
|
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 (wl_state.x_display && wwin->client_win) {
|
|
WMNativeAtom type;
|
|
int format;
|
|
unsigned long items, rest;
|
|
unsigned char *prop = NULL;
|
|
WMNativeAtom opacity_atom = XInternAtom(wl_state.x_display,
|
|
"_NET_WM_WINDOW_OPACITY", False);
|
|
if (XGetWindowProperty(wl_state.x_display, (WNativeWindow)wwin->client_win,
|
|
opacity_atom, 0L, 1L, False, AnyPropertyType,
|
|
&type, &format, &items, &rest, &prop) == Success
|
|
&& format == 32 && items == 1 && prop) {
|
|
unsigned long val = *(unsigned long *)prop;
|
|
alpha = (float)val / (float)0xFFFFFFFF;
|
|
}
|
|
if (prop)
|
|
XFree(prop);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|