decompose wl_backend.c: extract wl_dock.c

This commit is contained in:
Window Maker 2026-06-02 15:49:44 +02:00
parent 698d4dfba7
commit 5a2faa765a
3 changed files with 326 additions and 305 deletions

View File

@ -189,6 +189,7 @@ wmaker_SOURCES += \
backend/wayland/wl_pointer_focus.c \
backend/wayland/wl_grab.c \
backend/wayland/wl_keyboard.c \
backend/wayland/wl_dock.c \
backend/wayland/wl_input_fwd.c
endif

View File

@ -232,8 +232,8 @@ void W_UnregisterBacking(unsigned long id);
/* ------------------------------------------------------------------ */
#include "wl_types.h"
static void wm_dock_app_add(struct wl_toplevel_view *v);
static void wm_dock_app_remove(WNativeWindow id);
extern void wm_dock_app_add(struct wl_toplevel_view *v);
extern void wm_dock_app_remove(WNativeWindow id);
struct wl_compositor_state wl_state;
@ -341,7 +341,7 @@ static void handle_layer_shell_new_surface(struct wl_listener *listener, void *d
static void handle_virtual_pointer_new(struct wl_listener *listener, void *data);
/* Phase 17: XWayland new surface forward declaration. */
static void handle_xwayland_new_surface(struct wl_listener *listener, void *data);
static void wm_dock_create(WScreen *scr);
extern void wm_dock_create(WScreen *scr);
/* Phase 20: nested backend real input device forward declaration. */
extern void handle_backend_new_input(struct wl_listener *listener, void *data);
/* Phase 18: output lifecycle and frame rendering forward declarations. */
@ -2869,308 +2869,6 @@ wl_event_loop_terminate(void)
}
/* ================================================================== */
/* Phase 30: WM dock panel (layer-anchored, pixman-rendered) */
/* ================================================================== */
/*
* The dock is a vertical strip anchored to the right edge of the primary
* output. It is rendered into a wm_shm_surface whose pixels flow through
* the wlroots scene graph, exactly like any other WM UI element.
*
* Layout (right edge, full screen height):
*
* ┌──────────────────────────────────────────────────┬────────┐
* │ │ ██████ │
* │ desktop / client windows │ ██████ │
* │ (constrained to usable area) │ ██████ │
* │ │ dock │
* └──────────────────────────────────────────────────┴────────┘
* ← dw →
*
* The usable area (where windows are placed) is shrunk by dock_width so
* clients never overlap the panel.
*/
/* Default width when wPreferences.icon_size is not yet set. */
#define WM_DOCK_DEFAULT_WIDTH 68 /* 64 px icon + 4 px border */
/* Colours (ARGB8888, fully opaque) */
#define WM_DOCK_BG 0xFF1C1C1C /* dark charcoal background */
#define WM_DOCK_HIGHLIGHT 0xFF404040 /* 1-px highlight on left edge */
#define WM_DOCK_TOPSEP 0xFF303030 /* 1-px separator at top */
#define WM_DOCK_TILE_BG 0xFF252525 /* icon-slot background */
#define WM_DOCK_TILE_HI 0xFF353535 /* icon-slot top/left highlight */
#define WM_DOCK_TILE_SH 0xFF151515 /* icon-slot bottom/right shadow */
/* wm_dock_render_tile — paint one icon slot starting at (x, y).
* Slots are icon_size × icon_size with a 1-px inset bevel. */
static void
wm_dock_render_tile(struct wm_shm_surface *s,
int x, int y, int tile_sz)
{
/* Background */
wm_shm_surface_fill(s, x, y, tile_sz, tile_sz, WM_DOCK_TILE_BG);
/* Top highlight */
wm_shm_surface_fill(s, x, y, tile_sz, 1, WM_DOCK_TILE_HI);
/* Left highlight */
wm_shm_surface_fill(s, x, y, 1, tile_sz, WM_DOCK_TILE_HI);
/* Bottom shadow */
wm_shm_surface_fill(s, x, y + tile_sz - 1, tile_sz, 1, WM_DOCK_TILE_SH);
/* Right shadow */
wm_shm_surface_fill(s, x + tile_sz - 1, y, 1, tile_sz, WM_DOCK_TILE_SH);
}
/* wm_dock_render — paint the full dock panel.
* Draws the background, left-edge highlight, top separator, and a column
* of empty icon slots to give visual structure before real icons arrive. */
static void
wm_dock_render(struct wm_shm_surface *s, int icon_sz, int n_slots)
{
int w = s->width;
int h = s->height;
int tile_sz = icon_sz;
int pad = (w - tile_sz) / 2; /* horizontal centering margin */
/* Background */
wm_shm_surface_fill(s, 0, 0, w, h, WM_DOCK_BG);
/* Left-edge highlight (the dock's inner border with the desktop) */
wm_shm_surface_fill(s, 0, 0, 1, h, WM_DOCK_HIGHLIGHT);
/* Top separator */
wm_shm_surface_fill(s, 0, 0, w, 1, WM_DOCK_TOPSEP);
/* Icon-slot placeholders */
for (int i = 0; i < n_slots; i++) {
int ty = 2 + i * (tile_sz + 2);
if (ty + tile_sz > h)
break;
wm_dock_render_tile(s, pad, ty, tile_sz);
}
}
/* wm_dock_create — allocate the dock surface, render it, attach to scene,
* and shrink the head-0 usable area by dock_width pixels on the right. */
static void
wm_dock_create(WScreen *scr)
{
if (!wl_state.scene || !wl_state.output)
return;
int sw = (int)wl_state.output->width;
int sh = (int)wl_state.output->height;
int icon_sz = (wPreferences.icon_size > 0)
? wPreferences.icon_size : 64;
int dw = icon_sz + 4; /* dock width = icon + 2px pad each side */
int n_slots = (sh - 4) / (icon_sz + 2); /* how many slots fit */
struct wm_shm_surface *s = wm_shm_surface_create(dw, sh);
if (!s) {
wwarning("wl_backend: wm_dock_create: failed to allocate surface");
return;
}
wm_dock_render(s, icon_sz, n_slots);
/* Dock panel belongs in WL_LAYER_DOCK so it renders at the same
* depth as the dock icon frame_bufs (which wl_stacking_set_level
* places there for WMStatusWindowLevel=21). */
{
struct wlr_scene_tree *_dp = wl_state.level_trees[WL_LAYER_DOCK]
? wl_state.level_trees[WL_LAYER_DOCK] : &wl_state.scene->tree;
wm_shm_surface_commit(s, _dp, sw - dw, 0);
}
wl_state.dock_surface = s;
wl_state.dock_width = dw;
wl_state.dock_icon_sz = icon_sz;
wl_state.dock_n_slots = n_slots;
wl_state.dock_app_count = 0;
/* Shrink head-0 usable area so window placement avoids the dock. */
if (scr->usableArea) {
int right = scr->usableArea[0].x2 - dw;
if (right < scr->usableArea[0].x1)
right = scr->usableArea[0].x1;
scr->usableArea[0].x2 = right;
scr->totalUsableArea[0].x2 = right;
}
wwarning("wl_backend: dock panel created at %d,0 (%dx%d, %d slots)",
sw - dw, dw, sh, n_slots);
}
/* ------------------------------------------------------------------ */
/* Phase 30+: dock icon management */
/* ------------------------------------------------------------------ */
/*
* wm_shm_surface_repaint — after updating the pixman pixel data,
* force wlroots to re-upload the buffer to the GPU.
*
* wlroots 0.15 caches a texture per wlr_buffer pointer. Since our
* buffer is the same C object the cache entry stays valid even when
* pixel data changes. The reliable solution is to destroy the
* scene_buffer node and recreate it; wlroots then creates a fresh
* texture from the updated pixel data on the next frame.
*/
static void
wm_shm_surface_repaint(struct wm_shm_surface *s)
{
if (!s || !s->scene_buf) return;
int x = s->scene_buf->node.x;
int y = s->scene_buf->node.y;
struct wlr_scene_tree *parent = s->scene_buf->node.parent;
wlr_scene_node_destroy(&s->scene_buf->node);
s->scene_buf = NULL;
if (parent) {
s->scene_buf = wlr_scene_buffer_create(parent, &s->wlr_buf);
if (s->scene_buf)
wlr_scene_node_set_position(&s->scene_buf->node, x, y);
}
}
/*
* wm_dock_load_icon — read _NET_WM_ICON from an XWayland window via
* wrlib and return a heap-allocated ARGB8888 pixel array.
* Returns NULL for XDG native clients (no X11 icon available) or on
* failure; caller must free() the returned pointer when done.
*/
static uint32_t *
wm_dock_load_icon(struct wl_toplevel_view *v, int *out_w, int *out_h)
{
*out_w = *out_h = 0;
if (!v->xw_surface || !wl_state.x_display)
return NULL; /* XDG native: no X11 icon path */
RImage *ri = get_window_image_from_x11(wl_state.x_display, (WNativeWindow)v->id);
if (!ri)
return NULL;
int w = ri->width, h = ri->height;
int bpp = (ri->format == RRGBAFormat) ? 4 : 3;
uint32_t *argb = malloc((size_t)(w * h) * 4);
if (!argb) { RReleaseImage(ri); return NULL; }
unsigned char *p = ri->data;
for (int i = 0; i < w * h; i++, p += bpp) {
uint8_t r = p[0], g = p[1], b = p[2];
uint8_t a = (bpp == 4) ? p[3] : 255;
argb[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) |
((uint32_t)g << 8) | (uint32_t)b;
}
RReleaseImage(ri);
*out_w = w;
*out_h = h;
return argb;
}
/*
* wm_dock_refresh — repaint the dock surface with empty tiles, then
* overlay each managed app's icon into its slot, and re-upload to GPU.
*/
static void
wm_dock_refresh(void)
{
struct wm_shm_surface *s = wl_state.dock_surface;
if (!s) return;
int icon_sz = wl_state.dock_icon_sz;
int n_slots = wl_state.dock_n_slots;
int w = s->width;
int tile_sz = icon_sz;
int pad = (w - tile_sz) / 2;
/* Repaint background and all placeholder tiles */
wm_dock_render(s, icon_sz, n_slots);
/* Composite each app icon into its slot */
for (int i = 0; i < wl_state.dock_app_count && i < n_slots; i++) {
int iw = wl_state.dock_apps[i].icon_w;
int ih = wl_state.dock_apps[i].icon_h;
uint32_t *px = wl_state.dock_apps[i].icon_argb;
if (!px || iw <= 0 || ih <= 0)
continue;
pixman_image_t *src = pixman_image_create_bits(
PIXMAN_a8r8g8b8, iw, ih, px, iw * 4);
if (!src) continue;
/* Scale into the inset area (1px bevel border) */
int dst_sz = tile_sz - 2;
if (dst_sz < 1) dst_sz = 1;
pixman_transform_t xf;
pixman_transform_init_scale(&xf,
pixman_double_to_fixed((double)iw / dst_sz),
pixman_double_to_fixed((double)ih / dst_sz));
pixman_image_set_transform(src, &xf);
pixman_image_set_filter(src, PIXMAN_FILTER_BILINEAR, NULL, 0);
int ty = 2 + i * (tile_sz + 2);
if (ty + tile_sz > s->height) { pixman_image_unref(src); break; }
pixman_image_composite32(PIXMAN_OP_OVER,
src, NULL, s->image,
0, 0, 0, 0,
pad + 1, ty + 1,
dst_sz, dst_sz);
pixman_image_unref(src);
}
/* Re-upload to GPU */
wm_shm_surface_repaint(s);
}
/*
* wm_dock_app_add — called when a window is managed; adds it to the
* dock list, attempts to load its icon, and refreshes the dock panel.
*/
static void
wm_dock_app_add(struct wl_toplevel_view *v)
{
if (!wl_state.dock_surface) return;
if (wl_state.dock_app_count >= WM_DOCK_MAX_APPS) return;
int slot = wl_state.dock_app_count;
wl_state.dock_apps[slot].id = v->id;
wl_state.dock_apps[slot].icon_argb =
wm_dock_load_icon(v, &wl_state.dock_apps[slot].icon_w,
&wl_state.dock_apps[slot].icon_h);
wl_state.dock_app_count++;
wm_dock_refresh();
wwarning("wl_backend: dock: add id=0x%lx icon=%s slot=%d",
v->id,
wl_state.dock_apps[slot].icon_argb ? "loaded" : "none",
slot);
}
/*
* wm_dock_app_remove — called when a window is unmanaged; removes it
* from the dock list and refreshes the panel.
*/
static void
wm_dock_app_remove(WNativeWindow id)
{
if (!wl_state.dock_surface) return;
for (int i = 0; i < wl_state.dock_app_count; i++) {
if (wl_state.dock_apps[i].id != id)
continue;
free(wl_state.dock_apps[i].icon_argb);
int tail = wl_state.dock_app_count - i - 1;
if (tail > 0)
memmove(&wl_state.dock_apps[i],
&wl_state.dock_apps[i + 1],
(size_t)tail * sizeof(wl_state.dock_apps[0]));
wl_state.dock_app_count--;
wm_dock_refresh();
wwarning("wl_backend: dock: remove id=0x%lx total=%d",
id, wl_state.dock_app_count);
return;
}
}
/* ------------------------------------------------------------------ */

View File

@ -0,0 +1,322 @@
#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_output.h>
#include <wlr/util/log.h>
#include <pixman.h>
#include "../../screen.h"
#include "../x11/wmspec.h"
#include "wl_types.h"
#include <WINGs/WINGsP.h>
/* Forward declarations */
struct wm_shm_surface *wm_shm_surface_create(int width, int height);
void wm_shm_surface_fill(struct wm_shm_surface *s, int x, int y, int w, int h, uint32_t color);
void wm_shm_surface_commit(struct wm_shm_surface *s, struct wlr_scene_tree *parent, int x, int y);
/* Phase 30: WM dock panel (layer-anchored, pixman-rendered) */
/* ================================================================== */
/*
* The dock is a vertical strip anchored to the right edge of the primary
* output. It is rendered into a wm_shm_surface whose pixels flow through
* the wlroots scene graph, exactly like any other WM UI element.
*
* Layout (right edge, full screen height):
*
* ┌──────────────────────────────────────────────────┬────────┐
* │ │ ██████ │
* │ desktop / client windows │ ██████ │
* │ (constrained to usable area) │ ██████ │
* │ │ dock │
* └──────────────────────────────────────────────────┴────────┘
* ← dw →
*
* The usable area (where windows are placed) is shrunk by dock_width so
* clients never overlap the panel.
*/
/* Default width when wPreferences.icon_size is not yet set. */
#define WM_DOCK_DEFAULT_WIDTH 68 /* 64 px icon + 4 px border */
/* Colours (ARGB8888, fully opaque) */
#define WM_DOCK_BG 0xFF1C1C1C /* dark charcoal background */
#define WM_DOCK_HIGHLIGHT 0xFF404040 /* 1-px highlight on left edge */
#define WM_DOCK_TOPSEP 0xFF303030 /* 1-px separator at top */
#define WM_DOCK_TILE_BG 0xFF252525 /* icon-slot background */
#define WM_DOCK_TILE_HI 0xFF353535 /* icon-slot top/left highlight */
#define WM_DOCK_TILE_SH 0xFF151515 /* icon-slot bottom/right shadow */
/* wm_dock_render_tile — paint one icon slot starting at (x, y).
* Slots are icon_size × icon_size with a 1-px inset bevel. */
static void
wm_dock_render_tile(struct wm_shm_surface *s,
int x, int y, int tile_sz)
{
/* Background */
wm_shm_surface_fill(s, x, y, tile_sz, tile_sz, WM_DOCK_TILE_BG);
/* Top highlight */
wm_shm_surface_fill(s, x, y, tile_sz, 1, WM_DOCK_TILE_HI);
/* Left highlight */
wm_shm_surface_fill(s, x, y, 1, tile_sz, WM_DOCK_TILE_HI);
/* Bottom shadow */
wm_shm_surface_fill(s, x, y + tile_sz - 1, tile_sz, 1, WM_DOCK_TILE_SH);
/* Right shadow */
wm_shm_surface_fill(s, x + tile_sz - 1, y, 1, tile_sz, WM_DOCK_TILE_SH);
}
/* wm_dock_render — paint the full dock panel.
* Draws the background, left-edge highlight, top separator, and a column
* of empty icon slots to give visual structure before real icons arrive. */
static void
wm_dock_render(struct wm_shm_surface *s, int icon_sz, int n_slots)
{
int w = s->width;
int h = s->height;
int tile_sz = icon_sz;
int pad = (w - tile_sz) / 2; /* horizontal centering margin */
/* Background */
wm_shm_surface_fill(s, 0, 0, w, h, WM_DOCK_BG);
/* Left-edge highlight (the dock's inner border with the desktop) */
wm_shm_surface_fill(s, 0, 0, 1, h, WM_DOCK_HIGHLIGHT);
/* Top separator */
wm_shm_surface_fill(s, 0, 0, w, 1, WM_DOCK_TOPSEP);
/* Icon-slot placeholders */
for (int i = 0; i < n_slots; i++) {
int ty = 2 + i * (tile_sz + 2);
if (ty + tile_sz > h)
break;
wm_dock_render_tile(s, pad, ty, tile_sz);
}
}
/* wm_dock_create — allocate the dock surface, render it, attach to scene,
* and shrink the head-0 usable area by dock_width pixels on the right. */
void
wm_dock_create(WScreen *scr)
{
if (!wl_state.scene || !wl_state.output)
return;
int sw = (int)wl_state.output->width;
int sh = (int)wl_state.output->height;
int icon_sz = (wPreferences.icon_size > 0)
? wPreferences.icon_size : 64;
int dw = icon_sz + 4; /* dock width = icon + 2px pad each side */
int n_slots = (sh - 4) / (icon_sz + 2); /* how many slots fit */
struct wm_shm_surface *s = wm_shm_surface_create(dw, sh);
if (!s) {
wwarning("wl_backend: wm_dock_create: failed to allocate surface");
return;
}
wm_dock_render(s, icon_sz, n_slots);
/* Dock panel belongs in WL_LAYER_DOCK so it renders at the same
* depth as the dock icon frame_bufs (which wl_stacking_set_level
* places there for WMStatusWindowLevel=21). */
{
struct wlr_scene_tree *_dp = wl_state.level_trees[WL_LAYER_DOCK]
? wl_state.level_trees[WL_LAYER_DOCK] : &wl_state.scene->tree;
wm_shm_surface_commit(s, _dp, sw - dw, 0);
}
wl_state.dock_surface = s;
wl_state.dock_width = dw;
wl_state.dock_icon_sz = icon_sz;
wl_state.dock_n_slots = n_slots;
wl_state.dock_app_count = 0;
/* Shrink head-0 usable area so window placement avoids the dock. */
if (scr->usableArea) {
int right = scr->usableArea[0].x2 - dw;
if (right < scr->usableArea[0].x1)
right = scr->usableArea[0].x1;
scr->usableArea[0].x2 = right;
scr->totalUsableArea[0].x2 = right;
}
wwarning("wl_backend: dock panel created at %d,0 (%dx%d, %d slots)",
sw - dw, dw, sh, n_slots);
}
/* ------------------------------------------------------------------ */
/* Phase 30+: dock icon management */
/* ------------------------------------------------------------------ */
/*
* wm_shm_surface_repaint — after updating the pixman pixel data,
* force wlroots to re-upload the buffer to the GPU.
*
* wlroots 0.15 caches a texture per wlr_buffer pointer. Since our
* buffer is the same C object the cache entry stays valid even when
* pixel data changes. The reliable solution is to destroy the
* scene_buffer node and recreate it; wlroots then creates a fresh
* texture from the updated pixel data on the next frame.
*/
static void
wm_shm_surface_repaint(struct wm_shm_surface *s)
{
if (!s || !s->scene_buf) return;
int x = s->scene_buf->node.x;
int y = s->scene_buf->node.y;
struct wlr_scene_tree *parent = s->scene_buf->node.parent;
wlr_scene_node_destroy(&s->scene_buf->node);
s->scene_buf = NULL;
if (parent) {
s->scene_buf = wlr_scene_buffer_create(parent, &s->wlr_buf);
if (s->scene_buf)
wlr_scene_node_set_position(&s->scene_buf->node, x, y);
}
}
/*
* wm_dock_load_icon — read _NET_WM_ICON from an XWayland window via
* wrlib and return a heap-allocated ARGB8888 pixel array.
* Returns NULL for XDG native clients (no X11 icon available) or on
* failure; caller must free() the returned pointer when done.
*/
static uint32_t *
wm_dock_load_icon(struct wl_toplevel_view *v, int *out_w, int *out_h)
{
*out_w = *out_h = 0;
if (!v->xw_surface || !wl_state.x_display)
return NULL; /* XDG native: no X11 icon path */
RImage *ri = get_window_image_from_x11(wl_state.x_display, (WNativeWindow)v->id);
if (!ri)
return NULL;
int w = ri->width, h = ri->height;
int bpp = (ri->format == RRGBAFormat) ? 4 : 3;
uint32_t *argb = malloc((size_t)(w * h) * 4);
if (!argb) { RReleaseImage(ri); return NULL; }
unsigned char *p = ri->data;
for (int i = 0; i < w * h; i++, p += bpp) {
uint8_t r = p[0], g = p[1], b = p[2];
uint8_t a = (bpp == 4) ? p[3] : 255;
argb[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) |
((uint32_t)g << 8) | (uint32_t)b;
}
RReleaseImage(ri);
*out_w = w;
*out_h = h;
return argb;
}
/*
* wm_dock_refresh — repaint the dock surface with empty tiles, then
* overlay each managed app's icon into its slot, and re-upload to GPU.
*/
static void
wm_dock_refresh(void)
{
struct wm_shm_surface *s = wl_state.dock_surface;
if (!s) return;
int icon_sz = wl_state.dock_icon_sz;
int n_slots = wl_state.dock_n_slots;
int w = s->width;
int tile_sz = icon_sz;
int pad = (w - tile_sz) / 2;
/* Repaint background and all placeholder tiles */
wm_dock_render(s, icon_sz, n_slots);
/* Composite each app icon into its slot */
for (int i = 0; i < wl_state.dock_app_count && i < n_slots; i++) {
int iw = wl_state.dock_apps[i].icon_w;
int ih = wl_state.dock_apps[i].icon_h;
uint32_t *px = wl_state.dock_apps[i].icon_argb;
if (!px || iw <= 0 || ih <= 0)
continue;
pixman_image_t *src = pixman_image_create_bits(
PIXMAN_a8r8g8b8, iw, ih, px, iw * 4);
if (!src) continue;
/* Scale into the inset area (1px bevel border) */
int dst_sz = tile_sz - 2;
if (dst_sz < 1) dst_sz = 1;
pixman_transform_t xf;
pixman_transform_init_scale(&xf,
pixman_double_to_fixed((double)iw / dst_sz),
pixman_double_to_fixed((double)ih / dst_sz));
pixman_image_set_transform(src, &xf);
pixman_image_set_filter(src, PIXMAN_FILTER_BILINEAR, NULL, 0);
int ty = 2 + i * (tile_sz + 2);
if (ty + tile_sz > s->height) { pixman_image_unref(src); break; }
pixman_image_composite32(PIXMAN_OP_OVER,
src, NULL, s->image,
0, 0, 0, 0,
pad + 1, ty + 1,
dst_sz, dst_sz);
pixman_image_unref(src);
}
/* Re-upload to GPU */
wm_shm_surface_repaint(s);
}
/*
* wm_dock_app_add — called when a window is managed; adds it to the
* dock list, attempts to load its icon, and refreshes the dock panel.
*/
void
wm_dock_app_add(struct wl_toplevel_view *v)
{
if (!wl_state.dock_surface) return;
if (wl_state.dock_app_count >= WM_DOCK_MAX_APPS) return;
int slot = wl_state.dock_app_count;
wl_state.dock_apps[slot].id = v->id;
wl_state.dock_apps[slot].icon_argb =
wm_dock_load_icon(v, &wl_state.dock_apps[slot].icon_w,
&wl_state.dock_apps[slot].icon_h);
wl_state.dock_app_count++;
wm_dock_refresh();
wwarning("wl_backend: dock: add id=0x%lx icon=%s slot=%d",
v->id,
wl_state.dock_apps[slot].icon_argb ? "loaded" : "none",
slot);
}
/*
* wm_dock_app_remove — called when a window is unmanaged; removes it
* from the dock list and refreshes the panel.
*/
void
wm_dock_app_remove(WNativeWindow id)
{
if (!wl_state.dock_surface) return;
for (int i = 0; i < wl_state.dock_app_count; i++) {
if (wl_state.dock_apps[i].id != id)
continue;
free(wl_state.dock_apps[i].icon_argb);
int tail = wl_state.dock_app_count - i - 1;
if (tail > 0)
memmove(&wl_state.dock_apps[i],
&wl_state.dock_apps[i + 1],
(size_t)tail * sizeof(wl_state.dock_apps[0]));
wl_state.dock_app_count--;
wm_dock_refresh();
wwarning("wl_backend: dock: remove id=0x%lx total=%d",
id, wl_state.dock_app_count);
return;
}
}