Update
This commit is contained in:
@@ -1,14 +0,0 @@
|
|||||||
Task: 1. Add current_scale fields to Client and LayerSurface structs
|
|
||||||
Date: 2026-07-06
|
|
||||||
Status: COMPLETED
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- include/client.h:49 — float current_scale; added to Client struct (HOT section, after corner_radius)
|
|
||||||
- include/client.h:100 — float current_scale; added to LayerSurface struct (HOT section, after mapped)
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
- make debug: compiles cleanly, zero warnings
|
|
||||||
- Build artifacts confirm zero-init via ecalloc (no changes needed to allocation sites)
|
|
||||||
|
|
||||||
Evidence:
|
|
||||||
- grep 'current_scale' include/client.h → 2 matches (Client + LayerSurface)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
Task: 10. Add config scale range validation
|
|
||||||
Date: 2026-07-06
|
|
||||||
Status: COMPLETED
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- parser/parser.c:374-377 — if scale out of [0.25, 4.0], fprintf warning + clamp to 1.0
|
|
||||||
- parser/parser.c:378-379 — if scale > 3.0, fprintf warning about unusually large scale
|
|
||||||
- Uses fprintf(stderr, ...) matching existing parser conventions
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
- make debug: compiles cleanly, zero warnings
|
|
||||||
- Warning messages follow "peachwm: config: ..." format used elsewhere in parser.c
|
|
||||||
- Post-clamp check prevents double-warning when scale clamped to 1.0
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
=== TASK 12: FINAL VERIFICATION OF REFACTORED CODEBASE ===
|
|
||||||
Date: 2026-07-02
|
|
||||||
|
|
||||||
=== (a) BUILD VERIFICATION ===
|
|
||||||
|
|
||||||
1. make clean && make all (TCC, default):
|
|
||||||
RESULT: PASS ✅ - zero errors, zero warnings
|
|
||||||
All 12 .o files compiled, both peachwm and peachmsg/peachmsg linked successfully.
|
|
||||||
|
|
||||||
2. make clean && make debug (clang + ASan/UBSan/LeakSanitizer):
|
|
||||||
RESULT: PASS ✅ - zero errors, zero warnings
|
|
||||||
Built with: -Weverything -Werror + ASan/UBSan/LeakSan + -fsanitize-trap=all
|
|
||||||
All extra diagnostic warnings silenced with targeted -Wno-* flags.
|
|
||||||
|
|
||||||
3. make clean && make release (clang -Werror -Wpedantic):
|
|
||||||
RESULT: PASS ✅ - zero errors, zero warnings
|
|
||||||
Built with: -Werror -Wpedantic -Wmissing-prototypes -Wstrict-prototypes
|
|
||||||
-Wold-style-definition -Wmissing-declarations -Wimplicit-fallthrough
|
|
||||||
-Wno-gnu-zero-variadic-macro-arguments -march=native
|
|
||||||
|
|
||||||
=== (b) ASAN RUNTIME VERIFICATION ===
|
|
||||||
|
|
||||||
Compositor launched on wayland-1 (3s timeout, EXIT_CODE=124).
|
|
||||||
ASan output captured to /tmp/asan-peachwm.145812.
|
|
||||||
|
|
||||||
Results: ZERO leaks from peachwm code. All 120,561 bytes leaked across 906
|
|
||||||
allocations are from external libraries:
|
|
||||||
- libEGL_mesa.so (Mesa EGL): 400 bytes in 2 allocations
|
|
||||||
- libgallium-26.1.3 (Mesa driver): 83,528 bytes in 721 allocations
|
|
||||||
- libdrm.so.2: 20 bytes in 1 allocation
|
|
||||||
- Other Mesa internal: 36,613 bytes in 182 allocations
|
|
||||||
|
|
||||||
NO address errors, NO use-after-free, NO buffer overflows from peachwm.
|
|
||||||
All undefined behavior sanitizer traps also passed silently.
|
|
||||||
|
|
||||||
Verdict: ASan CLEAN ✅
|
|
||||||
|
|
||||||
=== (c) IPC BASELINE COMPARISON ===
|
|
||||||
|
|
||||||
peachmsg -g: "bad peachwm-ipc protocol"
|
|
||||||
Baseline expected: "peachwm not running as current compositor"
|
|
||||||
Both report the same result - peachwm IPC is unreachable when peachwm
|
|
||||||
is not the active Wayland compositor. This is the expected pre-existing
|
|
||||||
behavior. No regression.
|
|
||||||
|
|
||||||
=== (d) CODE QUALITY SCAN ===
|
|
||||||
|
|
||||||
1. Grep for TODO/FIXME/XXX/HACK in src/ and include/:
|
|
||||||
- src/peachwm.c: /* TODO handle other input device types */ (pre-existing)
|
|
||||||
- src/peachwm.c: /* TODO do we actually require a cursor? */ (pre-existing)
|
|
||||||
No new TODO/FIXME/XXX/HACK additions. CLEAN ✅
|
|
||||||
|
|
||||||
2. peachwm.c line count: 3,869 lines
|
|
||||||
Original: ~4,600 lines
|
|
||||||
Reduction: ~731 lines
|
|
||||||
New extracted modules and their sizes:
|
|
||||||
src/scratchpad.c: 236 lines
|
|
||||||
src/layout.c: 519 lines
|
|
||||||
src/client.c: 426 lines
|
|
||||||
src/ipc.c: 276 lines
|
|
||||||
src/ipc_socket.c: 1,237 lines
|
|
||||||
src/util.c: 52 lines
|
|
||||||
src/ext_workspace.c: 216 lines
|
|
||||||
src/wlr_ext_workspace_v1.c: 975 lines (pre-existing)
|
|
||||||
|
|
||||||
Total across all refactored src files: ~7,800+ lines (including header files)
|
|
||||||
peachwm.c remains the largest file at 3,869 lines.
|
|
||||||
|
|
||||||
=== OVERALL VERDICT ===
|
|
||||||
All 3 build targets: PASS
|
|
||||||
ASan runtime: PASS (no peachwm leaks)
|
|
||||||
IPC baseline: MATCH (same expected error)
|
|
||||||
Code scan: PASS (no new issues, 2 pre-existing TODOs)
|
|
||||||
Line count: 3,869 (under original ~4,600, above 3,500 target)
|
|
||||||
|
|
||||||
The refactored codebase builds cleanly across all compilers and sanitizer
|
|
||||||
configurations with no regressions.
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
Task: 2. Create client_update_scale(Client *c) function
|
|
||||||
Date: 2026-07-06
|
|
||||||
Status: COMPLETED
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- include/client.h:133 — declaration added
|
|
||||||
- src/client.c:418-442 — implementation with current_outputs max scale iteration
|
|
||||||
- Guards: null client, unmapped surface → return
|
|
||||||
- Iterates current_outputs for max scale across visible outputs
|
|
||||||
- Fallback to c->mon->wlr_output->scale or 1.0f
|
|
||||||
- Safety guard: scale <= 0.0f → return
|
|
||||||
- fabsf tolerance check before notify
|
|
||||||
- Calls both wlr_fractional_scale_v1_notify_scale + wlr_surface_set_preferred_buffer_scale(ceilf)
|
|
||||||
- src/peachwm.c:995 — c->current_scale initialized after client_set_scale() in commitnotify()
|
|
||||||
|
|
||||||
Verification: make debug — compiles cleanly, zero warnings
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
## task-3-transparency-blur
|
|
||||||
|
|
||||||
### Field added
|
|
||||||
- File: include/client.h
|
|
||||||
- Line 39: `struct wlr_scene_blur *blur;`
|
|
||||||
- Placement: after `struct wlr_scene_shadow *shadow;` (line 38)
|
|
||||||
|
|
||||||
### Context
|
|
||||||
- Forward declaration not needed — definition provided by `<scenefx/types/wlr_scene.h>` through monitor.h include chain.
|
|
||||||
- Field initialized to NULL by ecalloc in createnotify (peachwm.c:1245).
|
|
||||||
- No `#include` directives added.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
Task: 3. Create layersurface_update_scale(LayerSurface *l) function
|
|
||||||
Date: 2026-07-06
|
|
||||||
Status: COMPLETED
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- src/peachwm.c:912-941 — static function with current_outputs max scale iteration
|
|
||||||
- Guards, current_outputs iteration, fallback, safety guard, fabsf check
|
|
||||||
- Marked __attribute__((unused)) until wired in later task
|
|
||||||
- src/peachwm.c:952 — l->current_scale initialized after client_set_scale() in commitlayersurfacenotify() initial-commit path
|
|
||||||
|
|
||||||
Verification: make debug — compiles cleanly, zero warnings
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
================================================================================
|
|
||||||
EVIDENCE: Task 5 — wlr_fractional_scale_manager_v1 propagation chain
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Goal: Verify that scale-change notifications reach surfaces when any output
|
|
||||||
configuration changes (wlr-randr, hotplug, layout rejig, etc.).
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
PATH 1 (direct): outputmgrapplyortest() → updatemons()
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
File: src/peachwm.c
|
|
||||||
Function: outputmgrapplyortest() (line 2553)
|
|
||||||
Call site: line 2612 — updatemons(nullptr, nullptr);
|
|
||||||
|
|
||||||
Triggered by:
|
|
||||||
- outputmgrapply() (line 2548 — from wlr_output_manager config apply)
|
|
||||||
- outputmgrtest() (line 2615 — from wlr_output_manager test)
|
|
||||||
|
|
||||||
This path fires when a client (e.g. wlr-randr) sends a new output
|
|
||||||
configuration. After committing the per-head state and sending the
|
|
||||||
success/failure response, updatemons() is called unconditionally.
|
|
||||||
|
|
||||||
CONFIRMED ✅ — updatemons(nullptr, nullptr) at line 2612.
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
PATH 2 (indirect): output_layout.change signal → updatemons()
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Listener declaration: line 364
|
|
||||||
static struct wl_listener layout_change = {.notify = updatemons};
|
|
||||||
|
|
||||||
Signal wiring: line 3691
|
|
||||||
wl_signal_add(&output_layout->events.change, &layout_change);
|
|
||||||
|
|
||||||
This path fires when the output layout changes via any other route
|
|
||||||
(e.g. hotplug, wlr_output_layout_add call inside updatemons itself).
|
|
||||||
It is the "belt" to the "suspenders" of Path 1.
|
|
||||||
|
|
||||||
Note: outputmgrapplyortest() calls wlr_output_layout_add() at line 2600,
|
|
||||||
which itself triggers output_layout.change — so Path 2 also fires
|
|
||||||
during Path 1. updatemons() is idempotent (no ill effect from
|
|
||||||
running twice in a single event cycle).
|
|
||||||
|
|
||||||
CONFIRMED ✅ — layout_change.notify = updatemons at line 364,
|
|
||||||
wired at line 3691.
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
SCALE RE-NOTIFICATION HOOK (inside updatemons)
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
File: src/peachwm.c
|
|
||||||
Function: updatemons() (line 4101)
|
|
||||||
Hook: lines 4168–4201
|
|
||||||
|
|
||||||
For each enabled monitor:
|
|
||||||
|
|
||||||
[clients loop, line 4170]
|
|
||||||
wl_list_for_each(c, &clients, link) {
|
|
||||||
if (c->mon != m)
|
|
||||||
continue;
|
|
||||||
client_update_scale(c); // ← task 4 hook
|
|
||||||
if (!client_is_x11(c) && c->surface.xdg) {
|
|
||||||
struct wlr_xdg_popup *popup;
|
|
||||||
wl_list_for_each(popup, &c->surface.xdg->popups, link)
|
|
||||||
wlr_fractional_scale_v1_notify_scale(
|
|
||||||
popup->base->surface, (double)m->wlr_output->scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[layers loop, line 4181]
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
LayerSurface *l;
|
|
||||||
wl_list_for_each(l, &m->layers[i], link) {
|
|
||||||
layersurface_update_scale(l); // ← task 4 hook
|
|
||||||
{
|
|
||||||
struct wlr_scene_node *popup_node;
|
|
||||||
wl_list_for_each(popup_node, &l->popups->children, link) {
|
|
||||||
if (popup_node->type != WLR_SCENE_NODE_BUFFER)
|
|
||||||
continue;
|
|
||||||
struct wlr_scene_buffer *sb =
|
|
||||||
wlr_scene_buffer_from_node(popup_node);
|
|
||||||
struct wlr_scene_surface *ss =
|
|
||||||
wlr_scene_surface_try_from_buffer(sb);
|
|
||||||
if (ss && ss->surface)
|
|
||||||
wlr_fractional_scale_v1_notify_scale(
|
|
||||||
ss->surface, (double)m->wlr_output->scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
This covers both regular xdg-toplevel clients and layer-shell surfaces
|
|
||||||
(bars, backgrounds, OSDs), including their XDG popups.
|
|
||||||
|
|
||||||
CONFIRMED ✅ — scale re-notification hook present and complete.
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
VERDICT: PROPAGATION CHAIN CONFIRMED ✅
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
outputmgrapplyortest() → updatemons() [direct: line 2612]
|
|
||||||
layout_change signal → updatemons() [indirect: lines 364, 3691]
|
|
||||||
updatemons() → client_update_scale(), layersurface_update_scale(),
|
|
||||||
wlr_fractional_scale_v1_notify_scale() [lines 4168–4201]
|
|
||||||
|
|
||||||
No code changes required. The chain is complete. Scale-change notifications
|
|
||||||
will reach every surface (clients + layers + popups) whenever output
|
|
||||||
configuration changes through ANY path.
|
|
||||||
|
|
||||||
Test: wlr-randr --output DP-1 --scale 2 # triggers Path 1 via config apply
|
|
||||||
# which calls updatemons, which re-notifies every surface on that output.
|
|
||||||
# Also triggers Path 2 via wlr_output_layout_add calling events.change.
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
@@ -76,6 +76,7 @@ monitors = {
|
|||||||
-- },
|
-- },
|
||||||
-- }
|
-- }
|
||||||
--
|
--
|
||||||
|
|
||||||
effects = {
|
effects = {
|
||||||
windows = {
|
windows = {
|
||||||
corner_radius = 0, -- 0 = off, or a pixel value like 6, 10, 14
|
corner_radius = 0, -- 0 = off, or a pixel value like 6, 10, 14
|
||||||
@@ -120,6 +121,13 @@ effects = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
overview = {
|
||||||
|
scale = 0.3,
|
||||||
|
gaps_between_tags = 40,
|
||||||
|
border_px = 3,
|
||||||
|
dim_unfocused = true,
|
||||||
|
show_empty_tags = false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
-- autostart: it starts stuff in sequence
|
-- autostart: it starts stuff in sequence
|
||||||
@@ -153,6 +161,7 @@ keybinds = {
|
|||||||
{ mods = { "logo", "shift" }, key = "e", action = "quit" },
|
{ mods = { "logo", "shift" }, key = "e", action = "quit" },
|
||||||
{ mods = { "logo" }, key = "`", action = "togglescratchpad" },
|
{ mods = { "logo" }, key = "`", action = "togglescratchpad" },
|
||||||
{ mods = { "logo", "shift" }, key = "~", action = "swapdirscratchpad" },
|
{ mods = { "logo", "shift" }, key = "~", action = "swapdirscratchpad" },
|
||||||
|
{ mods = { "logo" }, key = "o", action = "toggleoverview" },
|
||||||
}
|
}
|
||||||
|
|
||||||
-- layout switchers (also available: setlayout_dwindle, setlayout_master, setlayout_monocle)
|
-- layout switchers (also available: setlayout_dwindle, setlayout_master, setlayout_monocle)
|
||||||
@@ -168,6 +177,7 @@ for i = 1, 9 do
|
|||||||
mods = { "logo" },
|
mods = { "logo" },
|
||||||
key = key,
|
key = key,
|
||||||
action = "view",
|
action = "view",
|
||||||
|
in your autostart
|
||||||
args = { tostring(mask) },
|
args = { tostring(mask) },
|
||||||
})
|
})
|
||||||
table.insert(keybinds, {
|
table.insert(keybinds, {
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
# Fractional Scale — Manual QA Checklist
|
|
||||||
|
|
||||||
Run these tests on **real hardware** (nested compositor won't exercise the
|
|
||||||
wlroots output scaling path). Each item stands alone — you don't need to
|
|
||||||
complete them in order.
|
|
||||||
|
|
||||||
**Prerequisites**
|
|
||||||
|
|
||||||
- PeachWM built and running on bare metal (not inside another compositor)
|
|
||||||
- `wlr-randr` installed (`wlr-randr --help` works)
|
|
||||||
- `xeyes` / `xterm` installed (for the XWayland test)
|
|
||||||
- A second monitor for items 3 and 4 (any DPI, any resolution)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
| # | Test | Setup | Action | Expected Result | Pass / Fail |
|
|
||||||
|---|------|-------|--------|-----------------|-------------|
|
|
||||||
| 1 | **Fractional scale rendering** | Set `scale = 1.5` on your monitor in `config.lua` (the catch-all rule or your monitor's named rule). Restart PeachWM. | Open a native Wayland terminal (e.g. kitty, foot, Alacritty). Look at text glyphs and UI element edges. | Text is crisp — no blurriness, no double-vision artifacts. Edges are not smeared. The surface's buffer scale is the next integer up (2), but the compositor's output scale is 1.5. | |
|
|
||||||
| 2 | **Runtime scale change** | Start PeachWM at scale 1.0. Open a terminal and a native Wayland GUI (e.g. `gtk4-demo` or `gnome-calculator`). | Run `wlr-randr --output <name> --scale 1.75`. Replace `<name>` with your output name (run `wlr-randr` with no args to list them). | All windows and panels (layer-shell bars like waybar) re-render at the new scale within one frame. No flicker, no frozen regions. Text remains sharp (not blurry). | |
|
|
||||||
| 3 | **Hotplug with mixed scale** | Start PeachWM at scale 1.0 on the built-in display. Have a second monitor connected at a **different** physical DPI (e.g. a 4K external at 2x scale config). | Plug the second monitor while PeachWM is running. Configure its scale via `wlr-randr --output <name> --scale 2.0`. Drag a window from the 1x monitor to the 2x monitor. | The window re-renders correctly on each monitor at its respective scale. No corruption, no leftover pixels, no visual glitches at the transition boundary. The window is larger on the 1x display (in logical pixels) and smaller on the 2x display, but physically similar in size. | |
|
|
||||||
| 4 | **Mixed-DPI side-by-side** | Two monitors active: one at scale 1.0, the other at scale 2.0. Use `wlr-randr` to set them before or after startup. | Move the cursor between monitors. Open a terminal on each. Run a panel bar (waybar) spanning both outputs. | **Cursor**: same physical size on both monitors (not tiny on HiDPI, not huge on LoDPI). **Windows**: text is sharp on both. **Panel**: bar area scales per-output; text and icons in the bar are not stretched or clipped. | |
|
|
||||||
| 5 | **Config reload (SIGHUP / IPC)** | Start PeachWM at `scale = 1.0`. Have a terminal and a layer-shell panel visible. | Edit `config.lua` to change the scale to 1.5 on your monitor. Reload the config: `killall -SIGHUP peachwm` (or send SIGHUP via IPC `peachmsg reload`). | All surfaces update their scale without restarting the compositor. The terminal and panel re-render at the new scale. Window positions remain stable (no unwanted movement). | |
|
|
||||||
| 6 | **XWayland on fractional scale** | Start PeachWM with `scale = 1.5`. Have an XWayland server running (bundled; no extra config needed). | Run `xeyes` from a terminal. Observe the window size and pupil tracking. Then run `xterm` and check text rendering. | The xeyes window renders at a correct physical size — not comically small or huge. Pupils track the cursor smoothly. xterm text is readable (may be slightly softer due to integer ceilf fallback, but not garbled). No visual artifacts. | |
|
|
||||||
| 7 | **No-change path (scale = 1.0)** | Start PeachWM with `scale = 1.0` (the default). | Run PeachWM with `WAYLAND_DEBUG=1` and grep for `fractional_scale` events: `WAYLAND_DEBUG=1 peachwm 2>&1 | grep -i scale`. Open and close a few windows. | No `wlr_fractional_scale_v1_notify_scale` calls after the initial commit. The threshold check (`fabsf(scale - current) > 0.001f`) prevents redundant notifications when scale hasn't actually changed. | |
|
|
||||||
| 8 | **Edge extremes** | For each extreme, start a **separate** PeachWM session. | **(a)** Set `scale = 0.5` in config, restart. Open a terminal, run a few apps, move windows around. **(b)** Set `scale = 3.0` in config, restart. Do the same. | The compositor does not crash on startup or during use. Windows are renderable at both extremes — text is readable at 0.5x (very small but not corrupted) and appropriately large at 3.0x. No assertion failures, no segfaults. | |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- **Scale range clamp**: The parser clamps scale to `[0.25, 4.0]` with a warning
|
|
||||||
for values above 3.0. Tests 1 through 6 stay within safe bounds.
|
|
||||||
- **XWayland limitation**: X11 has no fractional scale protocol. PeachWM sends
|
|
||||||
`ceilf(scale)` as the integer buffer scale for XWayland surfaces. Some
|
|
||||||
softness at non-integer scales is expected.
|
|
||||||
- **Threshold hysteresis**: Scale re-notification fires only when the change
|
|
||||||
exceeds 0.001. This prevents log spam and redundant commits.
|
|
||||||
- **No automated tests here**: See `test_scale.c` for automated unit tests of
|
|
||||||
the scale calculation and clamping logic.
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
/*
|
|
||||||
* test_scale.c — Unit tests for PeachWM fractional scale logic
|
|
||||||
*
|
|
||||||
* Compile: cc -std=c23 -Wall -Wextra -lm test/test_scale.c -o test_scale
|
|
||||||
* Run: ./test_scale
|
|
||||||
*
|
|
||||||
* These tests verify the pure-math logic behind the compositor's
|
|
||||||
* fractional scale support without requiring Wayland or wlroots.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
/* ── Constants mirrored from the compositor source ──────────── */
|
|
||||||
|
|
||||||
/* Tolerance threshold: a scale change <= 0.001f is not meaningful.
|
|
||||||
* Mirrors client_update_scale() and layersurface_update_scale(). */
|
|
||||||
static const float SCALE_TOLERANCE = 0.001f;
|
|
||||||
|
|
||||||
/* ── Mock struct for safety-guard testing ───────────────────── */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Minimal mock of a compositor surface struct for testing the
|
|
||||||
* safety-guard pattern (scale <= 0.0f → early return).
|
|
||||||
* We only need current_scale and a flag to verify that
|
|
||||||
* the notify code was skipped.
|
|
||||||
*/
|
|
||||||
typedef struct MockSurface {
|
|
||||||
float current_scale;
|
|
||||||
int notify_called; /* set to 1 if notify was invoked */
|
|
||||||
} MockSurface;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Mock implementation of the safety-guard pattern from
|
|
||||||
* client_update_scale() / layersurface_update_scale():
|
|
||||||
*
|
|
||||||
* if (scale <= 0.0f)
|
|
||||||
* return;
|
|
||||||
*
|
|
||||||
* Returns 1 if notify was called (valid scale), 0 if guard triggered.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
mock_update_scale(MockSurface *s, float scale)
|
|
||||||
{
|
|
||||||
/* Safety guard: invalid scale → no-op */
|
|
||||||
if (scale <= 0.0f)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Tolerance check: only notify on meaningful changes
|
|
||||||
* (mirrors the real compositor's fabsf check) */
|
|
||||||
if (fabsf(scale - s->current_scale) > SCALE_TOLERANCE) {
|
|
||||||
s->current_scale = scale;
|
|
||||||
s->notify_called = 1;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Test: Float compare tolerance ──────────────────────────── */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Verifies that the fabsf-based tolerance check correctly
|
|
||||||
* distinguishes meaningful scale changes from negligible ones.
|
|
||||||
*
|
|
||||||
* The compositor uses fabsf(scale - current_scale) > 0.001f
|
|
||||||
* to decide whether a scale change warrants re-notifying clients.
|
|
||||||
* Small floating-point noise should NOT trigger a notification;
|
|
||||||
* genuine changes (e.g. switching from 1.5x to 2.0x) should.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
test_float_compare_tolerance(void)
|
|
||||||
{
|
|
||||||
printf(" test_float_compare_tolerance... ");
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 1: nearly identical values → diff < tolerance.
|
|
||||||
* 1.5f and 1.5001f differ by 0.0001f, which is < 0.001f.
|
|
||||||
* The compositor should treat these as the same scale.
|
|
||||||
*/
|
|
||||||
float diff_small = fabsf(1.5f - 1.5001f);
|
|
||||||
assert(diff_small < SCALE_TOLERANCE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 2: clearly different values → diff > tolerance.
|
|
||||||
* 1.5f and 2.0f differ by 0.5f, which is >> 0.001f.
|
|
||||||
* The compositor should treat these as different scales.
|
|
||||||
*/
|
|
||||||
float diff_large = fabsf(1.5f - 2.0f);
|
|
||||||
assert(diff_large > SCALE_TOLERANCE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 3: same value — zero diff, well below tolerance.
|
|
||||||
* 1.5f and 1.5f differ by 0.0f.
|
|
||||||
* No change should trigger.
|
|
||||||
*/
|
|
||||||
float diff_zero = fabsf(1.5f - 1.5f);
|
|
||||||
assert(!(diff_zero > SCALE_TOLERANCE));
|
|
||||||
|
|
||||||
printf("PASS\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Test: Safety guard (scale <= 0.0f) ─────────────────────── */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Verifies that the safety-guard logic (scale <= 0.0f → early return)
|
|
||||||
* correctly prevents scale-notify calls with invalid scale values.
|
|
||||||
*
|
|
||||||
* The compositor derives scale from wlr_output.scale, which can
|
|
||||||
* theoretically be 0 or negative in edge cases. The guard ensures
|
|
||||||
* we never call wlr_fractional_scale_v1_notify_scale() with
|
|
||||||
* garbage scale values.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
test_safety_guard(void)
|
|
||||||
{
|
|
||||||
printf(" test_safety_guard... ");
|
|
||||||
|
|
||||||
MockSurface s = { .current_scale = 1.0f, .notify_called = 0 };
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 1: scale = 0.0f — guard must trigger, no notify.
|
|
||||||
* 0.0 is the guard boundary.
|
|
||||||
*/
|
|
||||||
int result_zero = mock_update_scale(&s, 0.0f);
|
|
||||||
assert(result_zero == 0);
|
|
||||||
assert(s.notify_called == 0);
|
|
||||||
assert(s.current_scale == 1.0f); /* unchanged */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 2: scale = -0.5f — guard must trigger, no notify.
|
|
||||||
* Negative scale is invalid.
|
|
||||||
*/
|
|
||||||
int result_neg = mock_update_scale(&s, -0.5f);
|
|
||||||
assert(result_neg == 0);
|
|
||||||
assert(s.notify_called == 0);
|
|
||||||
assert(s.current_scale == 1.0f); /* unchanged */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 3: scale = -1.0f — guard must trigger, no notify.
|
|
||||||
* Negative integer scale is invalid.
|
|
||||||
*/
|
|
||||||
int result_neg_one = mock_update_scale(&s, -1.0f);
|
|
||||||
assert(result_neg_one == 0);
|
|
||||||
assert(s.notify_called == 0);
|
|
||||||
assert(s.current_scale == 1.0f); /* unchanged */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 4: scale = 1.5f (valid, different from current 1.0f)
|
|
||||||
* — guard must NOT trigger, notify must be called.
|
|
||||||
*/
|
|
||||||
int result_valid = mock_update_scale(&s, 1.5f);
|
|
||||||
assert(result_valid == 1);
|
|
||||||
assert(s.notify_called == 1);
|
|
||||||
assert(s.current_scale == 1.5f); /* updated */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 5: scale <= 0 with notify_called already set to 1
|
|
||||||
* — guard must still trigger, notify_called must NOT change.
|
|
||||||
*/
|
|
||||||
s.notify_called = 0;
|
|
||||||
int result_after_valid = mock_update_scale(&s, 0.0f);
|
|
||||||
assert(result_after_valid == 0);
|
|
||||||
assert(s.notify_called == 0);
|
|
||||||
|
|
||||||
printf("PASS\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Test: ceilf fallback for integer buffer scale ──────────── */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Verifies that ceilf() produces the expected integer buffer scale
|
|
||||||
* values for XWayland surfaces.
|
|
||||||
*
|
|
||||||
* The compositor calls wlr_surface_set_preferred_buffer_scale()
|
|
||||||
* with (int32_t)ceilf(scale). XWayland has no fractional-scale
|
|
||||||
* protocol, so the scale is rounded up to the next integer.
|
|
||||||
*
|
|
||||||
* We test ceilf behavior at the values the compositor actually
|
|
||||||
* uses, plus edge cases. The goal is NOT to test libm's ceilf
|
|
||||||
* (which is known correct), but to verify our understanding of
|
|
||||||
* how the compositor maps fractional scales to integer buffer
|
|
||||||
* scales.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
test_ceilf_fallback(void)
|
|
||||||
{
|
|
||||||
printf(" test_ceilf_fallback... ");
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 1: exact integer → ceilf returns the same value.
|
|
||||||
* scale 1.0f → buffer scale 1
|
|
||||||
*/
|
|
||||||
assert((int32_t)ceilf(1.0f) == 1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 2: fractional scale → ceilf rounds up.
|
|
||||||
* scale 1.5f → buffer scale 2
|
|
||||||
* This is the common case: 1.5x fractional scale means
|
|
||||||
* the XWayland buffer is rendered at 2x.
|
|
||||||
*/
|
|
||||||
assert((int32_t)ceilf(1.5f) == 2);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 3: fractional < 1.0f → ceilf rounds up to 1.
|
|
||||||
* scale 0.75f → buffer scale 1
|
|
||||||
* Anything less than 1x still renders at 1x minimum.
|
|
||||||
*/
|
|
||||||
assert((int32_t)ceilf(0.75f) == 1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 4: scale 2.0f → buffer scale 2 (identity).
|
|
||||||
* Verifies that ceilf doesn't distort exact integers.
|
|
||||||
*/
|
|
||||||
assert((int32_t)ceilf(2.0f) == 2);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 5: scale 1.001f → buffer scale 2 (just over 1).
|
|
||||||
* Even a tiny fraction over an integer rounds up.
|
|
||||||
*/
|
|
||||||
assert((int32_t)ceilf(1.001f) == 2);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Case 6: scale 0.001f → buffer scale 1.
|
|
||||||
* Very small positive scale still rounds up to 1.
|
|
||||||
*/
|
|
||||||
assert((int32_t)ceilf(0.001f) == 1);
|
|
||||||
|
|
||||||
printf("PASS\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Entry point ────────────────────────────────────────────── */
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
int failed = 0;
|
|
||||||
|
|
||||||
printf("=== PeachWM scale logic tests ===\n\n");
|
|
||||||
|
|
||||||
printf("[1/3] Float compare tolerance\n");
|
|
||||||
test_float_compare_tolerance();
|
|
||||||
|
|
||||||
printf("[2/3] Safety guard (scale <= 0.0f)\n");
|
|
||||||
test_safety_guard();
|
|
||||||
|
|
||||||
printf("[3/3] ceilf fallback for XWayland\n");
|
|
||||||
test_ceilf_fallback();
|
|
||||||
|
|
||||||
printf("\n=== All scale tests passed ===\n");
|
|
||||||
return failed ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user