Skip to content

Commit 6c6d758

Browse files
authored
Merge pull request #17 from Nomadcxx/fix/restore-ipc-state-compat
Fix restore and IPC state controls
2 parents 7e304a1 + d6ceae2 commit 6c6d758

7 files changed

Lines changed: 269 additions & 21 deletions

File tree

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ builddir/
88
.wrap-db/
99
meson-logs/
1010
meson-private/
11+
compile_commands.json
1112

1213
# Compiled binaries
1314
gslapper
@@ -29,9 +30,20 @@ gslapper-holder
2930
*~
3031
*.swp
3132
*.swo
33+
*.tmp
34+
*.temp
35+
*.orig
36+
*.rej
37+
*.save
3238
.DS_Store
3339
Thumbs.db
3440

41+
# Python/cache files used by docs and local tooling
42+
__pycache__/
43+
*.py[cod]
44+
.pytest_cache/
45+
.mypy_cache/
46+
3547
# IDE files
3648
.vscode/
3749
.idea/
@@ -57,6 +69,8 @@ adorable-black-kitten*
5769
# Backup files
5870
*.bak
5971
*.backup
72+
*.backup.*
73+
*.fixed
6074

6175
# Generated protocol files
6276
*-protocol.c

docs/development/api-reference.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ ipc_send_response(client_fd, "STATUS: playing video /path/to/video.mp4\n");
206206
|---------|------------|----------|-------------|
207207
| `pause` | None | `OK` | Pause video playback |
208208
| `resume` | None | `OK` | Resume paused playback |
209-
| `stop` | None | `OK` | Stop gSlapper (exits) |
209+
| `stop`, `quit` | None | `OK` | Stop gSlapper (exits) |
210+
| `save-state` | None | `OK: state saved` | Save current wallpaper state |
210211
| `query` | None | `STATUS: <state> <type> <path>` | Get current wallpaper state |
211212
| `change <path>` | File path | `OK` or `OK: transition started` | Change wallpaper |
212213
| `layer <name>` | `background`, `bottom`, `top`, `overlay` | `OK` or `ERROR` | Change Wayland layer at runtime |
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Handover: Restore, IPC, and Consumer Compatibility
2+
3+
Date: 2026-07-08
4+
Branch: `fix/restore-ipc-state-compat`
5+
PR: https://github.com/Nomadcxx/gSlapper/pull/17
6+
Implementation commit: `85d232d`
7+
8+
## Scope
9+
10+
This session focused on pre-release correctness around restore/state behavior and compatibility with projects that invoke gSlapper as a backend. The work intentionally stayed narrow: command-line restore handling, IPC command behavior, state file durability, documentation alignment, and consumer smoke checks.
11+
12+
## Changes Made
13+
14+
### Restore CLI
15+
16+
- `gslapper --restore` now accepts the documented restore shape without requiring a normal wallpaper positional argument.
17+
- `gslapper --restore [output]` is now shown in `--help`.
18+
- Wildcard/all-output restore selectors (`*`, `all`, `All`, `ALL`) restore from the default `state.txt` instead of an output-specific `state-_.txt`.
19+
- `--state-file PATH` is now honored during restore.
20+
- If restore fails and no fallback wallpaper was supplied, gSlapper exits with a clear error instead of falling through ambiguously.
21+
- Restore with a fallback wallpaper remains supported: `gslapper --restore <output> <fallback>`.
22+
23+
### State Saving
24+
25+
- State saving for wildcard/all-output runs now records the logical selector while still using the default state file path.
26+
- The atomic writer in `src/state.c` now uses a unique temporary file (`mkstemp`) instead of a fixed `<state>.tmp` path.
27+
- This fixes a real local failure where an abandoned `state.txt.tmp` permanently blocked all future saves with `Failed to create temp state file`.
28+
- Temp files are still created in the same directory and renamed into place, preserving atomic replacement semantics.
29+
- Temp state file permissions are explicitly set to `0600`.
30+
31+
### IPC
32+
33+
- Added IPC `quit` as an alias for `stop`.
34+
- Added the documented IPC `save-state` command.
35+
- Updated IPC help text to list `stop, quit` and `save-state`.
36+
- Fixed IPC response fd ownership:
37+
- The client thread now duplicates the accepted client fd for each queued command.
38+
- The main loop owns that duplicate until it sends the response.
39+
- The queued command cleanup closes the response fd.
40+
- This fixes the observed behavior where `socat` received an empty response and gSlapper logged `Failed to send IPC response: Bad file descriptor`.
41+
- Multiple newline-delimited commands on one socket connection were tested and still work.
42+
43+
### Documentation
44+
45+
- Updated `docs/user-guide/ipc-control.md`:
46+
- Documents `stop` / `quit`.
47+
- Documents IPC `save-state`.
48+
- Updated `docs/development/api-reference.md`:
49+
- Adds `quit` beside `stop`.
50+
- Adds `save-state`.
51+
52+
## Consumer Compatibility Assessment
53+
54+
### Waytrogen
55+
56+
Repository checked: https://github.com/nikolaizombie1/waytrogen
57+
58+
Current Waytrogen gSlapper integration invokes gSlapper approximately as:
59+
60+
```bash
61+
gslapper -I /tmp/gslapper.sock [-p|-s] -o "<scale loop no-audio additional>" -f <monitor|*> <image>
62+
```
63+
64+
It also tries to stop an existing gSlapper instance with:
65+
66+
```bash
67+
echo quit | socat - UNIX-CONNECT:/tmp/gslapper.sock
68+
```
69+
70+
and falls back to `pkill -9 gslapper`.
71+
72+
Assessment:
73+
74+
- The launch shape remains compatible.
75+
- The new IPC `quit` alias directly supports Waytrogen's graceful shutdown path.
76+
- Before this fix, Waytrogen would rely on its forced-kill fallback because gSlapper did not handle `quit`.
77+
- After this fix, `quit` returns `OK` and exits cleanly in local live IPC testing.
78+
79+
### Waypaper
80+
81+
Repository checked: https://github.com/anufrievroman/waypaper
82+
83+
Current Waypaper gSlapper integration invokes gSlapper approximately as:
84+
85+
```bash
86+
gslapper --fork -o "loop <fill-mode> [no-audio] [user-options]" <monitor|*> <path>
87+
```
88+
89+
It does not use gSlapper IPC for normal operation. It stops gSlapper with process killing (`killall gslapper` for all outputs or monitor-pattern matching for a specific output).
90+
91+
Assessment:
92+
93+
- The launch shape remains compatible.
94+
- Local smoke tests started Waypaper-shaped commands with these option variants: `panscan=1.0`, `original`, `stretch`, and `fill`.
95+
- No parser, startup, or immediate runtime errors were observed for those command shapes.
96+
- There is one semantic mismatch worth noting: Waypaper maps its UI `fill` option to gSlapper `panscan=1.0`. In current gSlapper semantics, `panscan=1.0` behaves like contain/fit, while gSlapper `fill` is cover/crop. This is not a gSlapper release blocker, but it is a good future upstream fix for Waypaper's backend mapping.
97+
98+
## Local Verification
99+
100+
Ran on this laptop against the rebuilt local binary:
101+
102+
```bash
103+
ninja -C build
104+
tests/test_basic.sh
105+
```
106+
107+
Result:
108+
109+
- Build passed.
110+
- Basic integration tests passed: 16 passed, 0 failed.
111+
- Existing warning remains in `output_description`: discards `const` qualifier. This was left out of scope.
112+
113+
Live checks performed:
114+
115+
- `gslapper --restore --state-file /tmp/gslapper-definitely-missing`
116+
- Exits `1` with explicit missing-state/fallback error.
117+
- `gslapper --restore '*'`
118+
- No longer fails with the old positional-argument error.
119+
- IPC over Unix socket:
120+
- `query` returns status.
121+
- `save-state` returns `OK: state saved`.
122+
- `help` lists the expected commands.
123+
- `quit` returns `OK` and exits.
124+
- Multi-command socket input returned responses correctly.
125+
- Waypaper-shaped launch commands:
126+
- `loop panscan=1.0 no-audio`
127+
- `loop original no-audio`
128+
- `loop stretch no-audio`
129+
- `loop fill no-audio`
130+
131+
The rebuilt binary was installed to the active user path:
132+
133+
```bash
134+
/home/nomadx/.local/bin/gslapper
135+
```
136+
137+
The installed user-local binary hash matched `build/gslapper` after installation.
138+
139+
## Process Hygiene
140+
141+
Extra smoke-test gSlapper processes were cleaned up. The only gSlapper process intentionally left running was the existing sysc-greet wallpaper instance:
142+
143+
```bash
144+
gslapper -f -I /tmp/sysc-greet-wallpaper.sock * /usr/share/sysc-greet/wallpapers/sysc-greet-dark.png
145+
```
146+
147+
## Follow-Ups
148+
149+
- Review PR #17 on a second environment before merging.
150+
- Consider an upstream Waypaper issue or PR to map Waypaper UI `fill` to gSlapper `fill` instead of `panscan=1.0`.
151+
- Consider a small follow-up cleanup for old backup files under `src/`, because they pollute search results with stale `clappie` and old IPC/state behavior.
152+
- Consider separately fixing the existing `output_description` `const` warning.

docs/user-guide/ipc-control.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,27 @@ echo "layer background" | nc -U /tmp/gslapper.sock
9090
!!! note "Compositor support"
9191
Dynamic layer switching requires layer-shell protocol support for `set_layer` (v2+). On older compositors, this command returns an error.
9292

93-
### `stop`
93+
### `stop` / `quit`
9494

9595
Stop gSlapper.
9696

9797
```bash
9898
echo "stop" | nc -U /tmp/gslapper.sock
99+
echo "quit" | nc -U /tmp/gslapper.sock
99100
```
100101

101102
**Response:** `OK` (gSlapper exits)
102103

104+
### `save-state`
105+
106+
Save the current wallpaper state without stopping gSlapper.
107+
108+
```bash
109+
echo "save-state" | nc -U /tmp/gslapper.sock
110+
```
111+
112+
**Response:** `OK: state saved`
113+
103114
### `set-transition <type>`
104115

105116
Set transition effect type. Options: `none`, `fade`.

src/ipc.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,27 @@ static bool ipc_validate_input(const char *input, int client_fd) {
5353
}
5454

5555
static void ipc_queue_command_internal(const char *cmd_line, int client_fd) {
56+
int response_fd = dup(client_fd);
57+
if (response_fd < 0) {
58+
cflp_error("Failed to duplicate IPC client FD: %s", strerror(errno));
59+
return;
60+
}
61+
5662
ipc_command_t *cmd = calloc(1, sizeof(ipc_command_t));
5763
if (!cmd) {
5864
cflp_error("Failed to allocate IPC command");
65+
close(response_fd);
5966
return;
6067
}
6168

6269
cmd->cmd_line = strdup(cmd_line);
6370
if (!cmd->cmd_line) {
6471
cflp_error("Failed to allocate IPC command string");
72+
close(response_fd);
6573
free(cmd);
6674
return;
6775
}
68-
cmd->client_fd = client_fd;
76+
cmd->client_fd = response_fd;
6977
cmd->next = NULL;
7078

7179
pthread_mutex_lock(&ipc_queue_mutex);

0 commit comments

Comments
 (0)