windowmaker-wl/src/backend/wayland/wl_framebuf.c

872 lines
33 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;
}
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;
RImage *icon = NULL;
if (button == fwin->right_button) {
if (!wl_state.btn_close_img)
wl_state.btn_close_img = RGetImageFromXPMData(
button->screen_ptr->rcontext, PRED_CLOSE_XPM);
icon = wl_state.btn_close_img;
} else if (button == fwin->left_button) {
if (!wl_state.btn_iconify_img)
wl_state.btn_iconify_img = RGetImageFromXPMData(
button->screen_ptr->rcontext, PRED_ICONIFY_XPM);
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);
}
}
}
}