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

756 lines
28 KiB
C

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <stdlib.h>
#include <pixman.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/xwayland.h>
#include <X11/Xlib.h>
#include <xcb/xcb_icccm.h>
#include "../../menu.h"
#include "../../balloon.h"
#include "../../framewin.h"
#include "../../texture.h"
#include "../../def_pixmaps.h"
#include <wlr/interfaces/wlr_buffer.h>
#include "../../screen.h"
#include "wl_types.h"
/* Forward declarations from sibling modules. */
struct wl_toplevel_view *wl_find_view_by_xdg_surface(struct wlr_xdg_surface *surf);
void wmaker_popup_apply(struct wl_toplevel_view *view);
void frame_buf_update_children_abs(WNativeWindow parent_id);
struct wl_frame_buf *frame_buf_find(WNativeWindow id);
extern void *W_RegisterBacking(unsigned long id, void *img);
extern void W_UnregisterBacking(unsigned long id);
struct wl_toplevel_view *wl_find_view_by_id(WNativeWindow id);
__attribute__((weak)) pixman_image_t *get_window_image_from_x11(Display *x_display, WNativeWindow win)
{
(void)x_display; (void)win; return NULL;
}
Bool
wl_window_fetch_name(WNativeWindow win, char **name_out)
{
struct wl_toplevel_view *v = wl_find_view_by_id(win);
const char *title = NULL;
if (v && v->xw_surface)
title = v->xw_surface->title;
else if (v && v->xdg_surface && v->xdg_surface->toplevel)
title = v->xdg_surface->toplevel->title;
if (title) {
*name_out = strdup(title);
return True;
}
*name_out = NULL;
return False;
}
Bool
wl_window_get_icon_name(WNativeWindow win, char **name_out)
{
struct wl_toplevel_view *v = wl_find_view_by_id(win);
const char *title = NULL;
if (v && v->xw_surface)
title = v->xw_surface->title;
else if (v && v->xdg_surface && v->xdg_surface->toplevel)
title = v->xdg_surface->toplevel->title;
if (title) {
*name_out = strdup(title);
return True;
}
*name_out = NULL;
return False;
}
void *
wl_window_get_icon_image(WNativeWindow win)
{
/* For XWayland clients, parse _NET_WM_ICON via the Xwayland display.
* For native Wayland clients there is no equivalent (the upcoming
* xdg-toplevel-icon protocol is not yet wired). */
if (!wl_state.x_display) return NULL;
return get_window_image_from_x11(wl_state.x_display, (WNativeWindow)win);
}
void
wl_window_map(WNativeWindow win)
{
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->scene_node) {
wlr_scene_node_set_enabled(&v->scene_node->node, true);
/* Ensure XWayland X11 window is mapped so it composites children
* (needed for dockapps whose content is in a child icon_window). */
/* For XWayland dockapps: map the X11 window and reparent the
* icon_window child into it so XWayland composites the content. */
if (v->xw_surface && wl_state.x_display && v->xw_surface->hints &&
(v->xw_surface->hints->flags & XCB_ICCCM_WM_HINT_STATE) &&
v->xw_surface->hints->initial_state == 0 /* WithdrawnState */) {
Window xwin = (Window)v->xw_surface->window_id;
XMapWindow(wl_state.x_display, xwin);
if (v->xw_surface->hints->flags & XCB_ICCCM_WM_HINT_ICON_WINDOW) {
Window icon_xwin = (Window)v->xw_surface->hints->icon_window;
XReparentWindow(wl_state.x_display, icon_xwin, xwin, 0, 0);
XMapWindow(wl_state.x_display, icon_xwin);
}
XFlush(wl_state.x_display);
}
return;
}
if (v)
return;
/* Frame buffer: enable its scene node and children. */
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
/* If this is a child, only enable if parent is enabled */
if (fb->parent_id) {
struct wl_frame_buf *parent = frame_buf_find(fb->parent_id);
if (parent && parent->scene_buf &&
!parent->scene_buf->node.enabled)
return; /* parent not visible yet */
}
wlr_scene_node_set_enabled(&fb->scene_buf->node, true);
if (fb->tree) {
wlr_scene_node_set_enabled(&fb->tree->node, true);
}
/* Also enable children (and grandchildren) */
struct wl_frame_buf *child;
wl_list_for_each(child, &wl_state.frame_list, link) {
if (child->parent_id == win && child->scene_buf) {
wlr_scene_node_set_enabled(&child->scene_buf->node, true);
struct wl_frame_buf *gc;
wl_list_for_each(gc, &wl_state.frame_list, link) {
if (gc->parent_id == child->id && gc->scene_buf)
wlr_scene_node_set_enabled(&gc->scene_buf->node, true);
}
}
}
return;
}
}
void
wl_window_map_raised(WNativeWindow win)
{
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->scene_node) {
wlr_scene_node_set_enabled(&v->scene_node->node, true);
if (v->frame_id) {
struct wl_frame_buf *fb = frame_buf_find(v->frame_id);
if (fb && fb->tree) {
wlr_scene_node_raise_to_top(&fb->tree->node);
return;
}
}
wlr_scene_node_raise_to_top(&v->scene_node->node);
return;
}
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
wlr_scene_node_set_enabled(&fb->scene_buf->node, true);
if (fb->tree) {
/* Top-level frame: enable tree (decoration children become visible). */
wlr_scene_node_set_enabled(&fb->tree->node, true);
wlr_scene_node_raise_to_top(&fb->tree->node);
} else {
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
}
return;
}
}
void wl_window_map_subwindows(WNativeWindow parent)
{
(void)parent;
/* Child windows are sub-regions of the frame buffer — no-op. */
}
void
wl_window_unmap(WNativeWindow win)
{
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->scene_node) {
wlr_scene_node_set_enabled(&v->scene_node->node, false);
return;
}
struct wl_frame_buf *fb = frame_buf_find(win);
if (!fb) return;
/* For top-level frames, disable the whole tree. This hides the frame
* buffer, all child scene_bufs (titlebar, resizebar, buttons), and the
* client surface reparented into fb->tree — atomically and irrevocably
* until the frame is explicitly mapped again. Disabling individual
* child nodes is insufficient because a subsequent wl_frame_paint can
* re-enable them while the window is still iconified. */
if (fb->tree) {
wlr_scene_node_set_enabled(&fb->tree->node, false);
return;
}
/* Child frame_buf: disable its scene_buf node only. */
if (fb->scene_buf)
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
}
void wl_window_move(WNativeWindow win, int x, int y)
{
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v) {
if (v->frame_id) {
/* Client is a child of a frame — coords are relative to frame */
v->frame_rel_x = x;
v->frame_rel_y = y;
struct wl_frame_buf *fb = frame_buf_find(v->frame_id);
if (fb) {
v->x = fb->x + x;
v->y = fb->y + y;
}
} else {
v->x = x; v->y = y;
}
if (v->scene_node) {
if (v->frame_id)
wlr_scene_node_set_position(&v->scene_node->node, x, y);
else
wlr_scene_node_set_position(&v->scene_node->node, v->x, v->y);
}
if (v->xw_surface)
wlr_xwayland_surface_configure(v->xw_surface,
(int16_t)v->x, (int16_t)v->y,
v->xw_surface->width, v->xw_surface->height);
/* Re-apply any popups whose parent is this toplevel so they
* track the parent's position. */
struct wl_toplevel_view *p;
wl_list_for_each(p, &wl_state.toplevel_list, link) {
if (p->popup && p->popup_parent
&& wl_find_view_by_xdg_surface(p->popup_parent->base) == v) {
wmaker_popup_apply(p);
}
}
return;
}
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb) {
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 */
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);
}
/* Update absolute position tracking for hit-testing — recursively
* for all descendants so grandchildren (e.g. WINGs panel inside
* a fake_leader inside a WMaker frame) stay in sync. */
frame_buf_update_children_abs(win);
return;
}
}
void wl_window_resize(WNativeWindow win, int w, int h)
{
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v) {
if (w <= 0 || h <= 0) return;
if (v->xw_surface) {
wlr_xwayland_surface_configure(v->xw_surface,
(int16_t)v->x, (int16_t)v->y, (uint16_t)w, (uint16_t)h);
return;
}
if (!v->xdg_surface || !v->xdg_surface->toplevel) return;
wlr_xdg_toplevel_set_size(v->xdg_surface->toplevel, (uint32_t)w, (uint32_t)h);
wlr_xdg_surface_schedule_configure(v->xdg_surface);
return;
}
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && w > 0 && h > 0 && (w != fb->width || h != fb->height)) {
if (fb->scene_buf)
wlr_scene_buffer_set_buffer(fb->scene_buf, NULL);
pixman_image_unref(fb->image);
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, w, h, NULL, 0);
fb->width = w;
fb->height = h;
fb->base.width = w;
fb->base.height = h;
W_RegisterBacking(fb->id, fb->image);
if (fb->scene_buf)
wlr_scene_buffer_set_dest_size(fb->scene_buf, w, h);
}
}
void wl_window_set_border(WNativeWindow win, unsigned long pixel)
{
(void)win; (void)pixel; /* borders are drawn into the frame buffer */
}
void wl_window_set_border_width(WNativeWindow win, int width)
{
(void)win; (void)width; /* no-op for native frames */
}
void wl_window_set_save_under(WNativeWindow win, int save_under) { (void)win; (void)save_under; }
void wl_noop_window_set_background(WNativeWindow win, unsigned long pixel) { (void)win; (void)pixel; }
extern void *W_GetViewBacking(unsigned long id);
void wl_window_set_background_pixmap(WNativeWindow win, unsigned long pixmap)
{
if (pixmap == 0) return;
struct wl_frame_buf *fb = frame_buf_find(win);
pixman_image_t *dst_img = NULL;
if (fb && fb->image) {
dst_img = fb->image;
fb->bg_pixmap = (pixman_image_t *)(uintptr_t)pixmap;
} else {
dst_img = (pixman_image_t *)W_GetViewBacking(win);
}
if (!dst_img) return;
pixman_image_t *src = (pixman_image_t *)(uintptr_t)pixmap;
int src_w = pixman_image_get_width(src);
int src_h = pixman_image_get_height(src);
/* Blit source pixmap into frame_buf's image */
pixman_image_composite32(PIXMAN_OP_SRC,
src, /* src */
NULL, /* mask */
dst_img, /* dest */
0, 0, /* src x,y */
0, 0, /* mask x,y */
0, 0, /* dest x,y */
src_w < pixman_image_get_width(dst_img) ? src_w : pixman_image_get_width(dst_img),
src_h < pixman_image_get_height(dst_img) ? src_h : pixman_image_get_height(dst_img));
if (fb)
wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL);
}
void wl_pixmap_free(unsigned long pixmap)
{
if (pixmap == 0) return;
pixman_image_t *img = (pixman_image_t *)(uintptr_t)pixmap;
pixman_image_unref(img);
}
void wl_window_clear(WNativeWindow win)
{
struct wl_frame_buf *fb = frame_buf_find(win);
pixman_image_t *img = NULL;
if (fb && fb->image) {
img = fb->image;
} else {
img = (pixman_image_t *)W_GetViewBacking(win);
}
if (!img) return;
int width = pixman_image_get_width(img);
int height = pixman_image_get_height(img);
/* If a background pixmap is set, repaint from it (like XClearWindow) */
if (fb && fb->bg_pixmap) {
pixman_image_composite32(PIXMAN_OP_SRC,
fb->bg_pixmap, NULL, img,
0, 0, 0, 0, 0, 0,
width, height);
} else {
/* No background pixmap — fill with transparent black */
int stride = pixman_image_get_stride(img);
uint32_t *data = (uint32_t *)pixman_image_get_data(img);
memset(data, 0, stride * height);
}
/* Notify the scene buffer of the update */
if (fb)
wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL);
}
int
wl_window_get_rect(WNativeWindow win,
int *win_x, int *win_y, int *win_w, int *win_h)
{
if (win == WNATIVE_NONE) {
if (win_x) { *win_x = 0; } if (win_y) { *win_y = 0; }
if (win_w) { *win_w = 1; } if (win_h) { *win_h = 1; }
return 0; /* invalid/none */
}
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->xw_surface) {
if (win_x) { *win_x = v->x; }
if (win_y) { *win_y = v->y; }
if (win_w) { *win_w = v->xw_surface->width > 0 ? v->xw_surface->width : 1; }
if (win_h) { *win_h = v->xw_surface->height > 0 ? v->xw_surface->height : 1; }
return 1;
}
if (v && v->xdg_surface && v->xdg_surface->surface) {
int w = v->xdg_surface->surface->current.width;
int h = v->xdg_surface->surface->current.height;
if (win_x) { *win_x = v->x; }
if (win_y) { *win_y = v->y; }
if (win_w) { *win_w = w > 0 ? w : 1; }
if (win_h) { *win_h = h > 0 ? h : 1; }
return 1;
}
/* Frame buffer geometry. */
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb) {
if (win_x) { *win_x = fb->x; }
if (win_y) { *win_y = fb->y; }
if (win_w) { *win_w = fb->width; }
if (win_h) { *win_h = fb->height; }
return 1;
}
/* Synthetic child IDs (titlebar, buttons) — return minimal geometry. */
if (win_x) { *win_x = 0; } if (win_y) { *win_y = 0; }
if (win_w) { *win_w = 1; } if (win_h) { *win_h = 1; }
return 0;
}
void wl_window_add_events(WNativeWindow win, unsigned long extra_mask) { (void)win; (void)extra_mask; }
void wl_window_select_events(WNativeWindow win, unsigned long mask) { (void)win; (void)mask; }
void wl_window_shape_clear(WWindow *wwin) { (void)wwin; }
void wl_window_shape_set(WWindow *wwin) { (void)wwin; }
void wl_menu_paint_entry(WMenu *menu, int index, int selected)
{
if (!menu || !menu->menu || !menu->menu->window)
return;
struct wl_frame_buf *fb = frame_buf_find((WNativeWindow)menu->menu->window);
if (!fb || !fb->image)
return;
/* Guard against painting before wMenuRealize: entry_height is 0,
* fb->image is the initial 1x10 placeholder, and the bevel-line
* computations below would write outside the pixman buffer.
* wMenuSetEnabled() in particular triggers paints on the brother
* during updateOptionsMenu before the menu is realized. */
if (menu->entry_height <= 0 || fb->width <= 1 || fb->height <= 10)
return;
WScreen *scr = menu->frame->screen_ptr;
WMenuEntry *entry = menu->entries[index];
int y = menu->entry_height * index;
int h = menu->entry_height;
int w = fb->width;
uint32_t *dst = (uint32_t *)pixman_image_get_data(fb->image);
int stride = pixman_image_get_stride(fb->image) / 4;
/* Background color from appearance preferences */
uint32_t bg;
if (selected) {
WMColor *sc = scr->select_color;
bg = 0xFF000000 | ((uint32_t)WMRedComponentOfColor(sc) >> 8 << 16)
| ((uint32_t)WMGreenComponentOfColor(sc) >> 8 << 8)
| ((uint32_t)WMBlueComponentOfColor(sc) >> 8);
} else {
WTexSolid *ts = (WTexSolid *)scr->menu_item_texture;
if (ts)
bg = 0xFF000000 | ((uint32_t)ts->normal.red >> 8 << 16)
| ((uint32_t)ts->normal.green >> 8 << 8)
| ((uint32_t)ts->normal.blue >> 8);
else
bg = 0xFFAEAAAE;
}
for (int row = y; row < y + h && row < fb->height; row++)
for (int col = 0; col < w; col++)
dst[row * stride + col] = bg;
/* Draw text using WINGs font backend (renders via PangoFT2 → pixman) */
WMColor *color = selected ? scr->select_text_color :
(entry->flags.enabled ? scr->mtext_color : scr->dtext_color);
/* Ensure text is readable against background: if text color is too
* close to bg (dark theme with black text), use white instead. */
if (color && !selected) {
unsigned tr = WMRedComponentOfColor(color) >> 8;
unsigned tg = WMGreenComponentOfColor(color) >> 8;
unsigned tb = WMBlueComponentOfColor(color) >> 8;
unsigned br = (bg >> 16) & 0xFF;
unsigned bg_ = (bg >> 8) & 0xFF;
unsigned bb = bg & 0xFF;
int text_lum = (int)(tr * 299 + tg * 587 + tb * 114) / 1000;
int bg_lum = (int)(br * 299 + bg_ * 587 + bb * 114) / 1000;
if (abs(text_lum - bg_lum) < 64)
color = (bg_lum < 128) ? scr->white : scr->black;
}
WMFont *font = scr->menu_entry_font;
WMDrawable d = (WMDrawable)(uintptr_t)fb->image;
if (entry->text && font && color) {
int text_y = y + 3;
WMDrawString(scr->wmscreen, d, color, font,
8, text_y, entry->text, strlen(entry->text));
}
if (entry->rtext && entry->cascade < 0 && font && color) {
int tw = WMWidthOfString(font, entry->rtext, strlen(entry->rtext));
int text_y = y + 3;
WMDrawString(scr->wmscreen, d, color, font,
w - 8 - tw, text_y, entry->rtext, strlen(entry->rtext));
}
/* Draw cascade arrow for submenus (3D right-pointing triangle matching X11) */
if (entry->cascade >= 0) {
WTexSolid *aux = (WTexSolid *)scr->menu_item_texture;
uint32_t dim_c = 0xFF606060;
uint32_t light_c = 0xFFE0E0E0;
uint32_t dark_c = 0xFF303030;
if (aux) {
dim_c = 0xFF000000 | ((uint32_t)aux->dim.red >> 8 << 16)
| ((uint32_t)aux->dim.green >> 8 << 8)
| ((uint32_t)aux->dim.blue >> 8);
light_c = 0xFF000000 | ((uint32_t)aux->light.red >> 8 << 16)
| ((uint32_t)aux->light.green >> 8 << 8)
| ((uint32_t)aux->light.blue >> 8);
dark_c = 0xFF000000 | ((uint32_t)aux->dark.red >> 8 << 16)
| ((uint32_t)aux->dark.green >> 8 << 8)
| ((uint32_t)aux->dark.blue >> 8);
}
/* Top edge: dim line from (w-11, y+6) to (w-6, y+h/2-1) */
int x0 = w - 11, y0 = y + 6, x1 = w - 6, y1 = y + h / 2 - 1;
for (int i = 0; i <= 5; i++) {
int lx = x0 + i * (x1 - x0) / 5;
int ly = y0 + i * (y1 - y0) / 5;
if (lx >= 0 && lx < (int)fb->width && ly >= 0 && ly < fb->height)
dst[ly * stride + lx] = dim_c;
}
/* Bottom edge: light line from (w-11, y+h-8) to (w-6, y+h/2-1) */
y0 = y + h - 8;
for (int i = 0; i <= 5; i++) {
int lx = x0 + i * (x1 - x0) / 5;
int ly = y0 + i * (y1 - y0) / 5;
if (lx >= 0 && lx < (int)fb->width && ly >= 0 && ly < fb->height)
dst[ly * stride + lx] = light_c;
}
/* Left edge: dark vertical line at w-12 from y+6 to y+h-8 */
int lx = w - 12;
if (lx >= 0 && lx < (int)fb->width)
for (int ly = y + 6; ly <= y + h - 8 && ly < fb->height; ly++)
if (ly >= 0)
dst[ly * stride + lx] = dark_c;
}
/* Bevel lines (separators between entries) */
uint32_t light = 0xFFDDDDDD;
uint32_t dark = 0xFF555555;
/* Left edge - light */
for (int row = y; row < y + h && row < fb->height; row++)
dst[row * stride] = light;
/* Right edge - dark */
for (int row = y; row < y + h && row < fb->height; row++)
if (w - 1 < fb->width)
dst[row * stride + (w - 1)] = dark;
/* Top line - light (skip first entry) */
if (index > 0 && y < fb->height)
for (int col = 0; col < w && col < fb->width; col++)
dst[y * stride + col] = light;
/* Bottom line - dark (skip last entry) */
if (index < menu->entry_no - 1 && y + h - 1 < fb->height)
for (int col = 0; col < w && col < fb->width; col++)
dst[(y + h - 1) * stride + col] = dark;
wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL);
}
void wl_menu_draw_frame(WScreen *scr, WNativeWindow win, int y, int w, int h, int type)
{
struct wl_frame_buf *fb = frame_buf_find(win);
if (!fb || !fb->image)
return;
uint32_t *dst = (uint32_t *)pixman_image_get_data(fb->image);
int stride = pixman_image_get_stride(fb->image) / 4;
uint32_t dark = 0xFF555555;
uint32_t light = 0xFFDDDDDD;
/* Right edge */
for (int row = y; row < y + h && row < fb->height; row++)
if (w - 1 < fb->width)
dst[row * stride + (w - 1)] = dark;
/* Bottom edge */
if (y + h - 1 < fb->height)
for (int col = 0; col < w && col < fb->width; col++)
dst[(y + h - 1) * stride + col] = dark;
/* Left edge */
for (int row = y; row < y + h && row < fb->height; row++)
dst[row * stride] = light;
/* Top edge */
if (y < fb->height)
for (int col = 0; col < w && col < fb->width; col++)
dst[y * stride + col] = light;
(void)scr; (void)type;
wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL);
}
void wl_balloon_init(WScreen *scr)
{
scr->balloon = wmalloc(sizeof(WBalloon));
memset(scr->balloon, 0, sizeof(WBalloon));
/* Create a frame_buf in the overlay layer for balloon tooltips */
if (wl_state.scene) {
struct wl_frame_buf *fb = wmalloc(sizeof(*fb));
memset(fb, 0, sizeof(*fb));
int w = 1, h = 1;
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, w, h, NULL, 0);
wlr_buffer_init(&fb->base, &frame_buf_impl, w, h);
fb->id = wl_state.next_synthetic_id++;
fb->width = w;
fb->height = h;
struct wlr_scene_tree *parent = wl_state.level_trees[WL_LAYER_OVERLAY]
? wl_state.level_trees[WL_LAYER_OVERLAY] : &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, 0, 0);
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->tree->node, false);
}
wl_list_insert(&wl_state.frame_list, &fb->link);
W_RegisterBacking(fb->id, fb->image);
scr->balloon->window = (WNativeWindow)fb->id;
}
}
void wl_balloon_show(WScreen *scr, void *bal_ptr)
{
WBalloon *bal = (WBalloon *)bal_ptr;
if (!bal->text || !bal->text[0])
return;
struct wl_frame_buf *fb = frame_buf_find((WNativeWindow)bal->window);
if (!fb)
return;
WMFont *font = scr->info_text_font;
int textlen = strlen(bal->text);
int tw = WMWidthOfString(font, bal->text, textlen);
int th = WMFontHeight(font);
int pad = 4;
int bw = tw + pad * 2;
int bh = th + pad * 2;
/* Resize pixman buffer if needed */
if (bw != fb->width || bh != fb->height) {
if (fb->scene_buf)
wlr_scene_buffer_set_buffer(fb->scene_buf, NULL);
pixman_image_unref(fb->image);
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, bw, bh, NULL, 0);
fb->width = bw;
fb->height = bh;
fb->base.width = bw;
fb->base.height = bh;
if (fb->scene_buf)
wlr_scene_buffer_set_dest_size(fb->scene_buf, bw, bh);
}
/* Fill pale yellow background */
uint32_t *data = pixman_image_get_data(fb->image);
for (int i = 0; i < bw * bh; i++)
data[i] = 0xFFFFFFE0;
/* 1px black border */
for (int x = 0; x < bw; x++) {
data[x] = 0xFF000000;
data[(bh - 1) * bw + x] = 0xFF000000;
}
for (int y = 0; y < bh; y++) {
data[y * bw] = 0xFF000000;
data[y * bw + bw - 1] = 0xFF000000;
}
/* Draw text */
WMDrawString(scr->wmscreen, (WMDrawable)(uintptr_t)fb->image, scr->black, font,
pad, pad, bal->text, textlen);
/* Position near objectWindow */
int bx = 0, by = 0;
struct wl_frame_buf *obj = frame_buf_find((WNativeWindow)bal->objectWindow);
if (obj) {
bx = obj->x;
by = obj->y + bal->h + 2;
}
/* Clamp to screen */
if (bx + bw > scr->scr_width)
bx = scr->scr_width - bw;
if (by + bh > scr->scr_height)
by = by - bal->h - bh - 2;
if (fb->scene_buf) {
wlr_scene_node_set_position(&fb->scene_buf->node, bx, by);
wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL);
wlr_scene_node_set_enabled(&fb->tree->node, true);
}
bal->mapped = 1;
}
void wl_context_init(void)
{
/* Wayland uses a hash table instead of X11 contexts */
w_global.context.client_win = 1;
w_global.context.app_win = 2;
w_global.context.stack = 3;
}
int wl_window_get_command(WNativeWindow win, char ***argv_out, int *argc_out)
{
/* Wayland native clients: return [app_id] as a single-arg command.
* Most app_ids match the binary name (e.g. "foot", "firefox") so this
* is sufficient for session restore via PATH lookup or a desktop-file
* Exec= line.
*
* XWayland clients: prefer WM_COMMAND (read from wl_state.x_display)
* and fall back to xw_surface->instance / class. */
*argv_out = NULL;
*argc_out = 0;
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (!v) return 0;
const char *cmd = NULL;
if (v->xw_surface) {
#if USE_XWAYLAND
/* XWayland: try WM_COMMAND first via the X server. */
if (wl_state.x_display && v->xw_surface->window_id) {
char **cmd_argv = NULL;
int cmd_argc = 0;
if (XGetCommand(wl_state.x_display, v->xw_surface->window_id,
&cmd_argv, &cmd_argc) && cmd_argc > 0 && cmd_argv) {
*argv_out = wmalloc(sizeof(char *) * cmd_argc);
for (int i = 0; i < cmd_argc; i++)
(*argv_out)[i] = wstrdup(cmd_argv[i]);
*argc_out = cmd_argc;
XFreeStringList(cmd_argv);
return 1;
}
}
#endif
cmd = v->xw_surface->instance ? v->xw_surface->instance :
v->xw_surface->class;
} else if (v->xdg_surface && v->xdg_surface->toplevel) {
cmd = v->xdg_surface->toplevel->app_id;
}
if (!cmd || !*cmd) return 0;
*argv_out = wmalloc(sizeof(char *));
(*argv_out)[0] = wstrdup(cmd);
*argc_out = 1;
return 1;
}