Merge pull request 'lemurs, clipmenu: X keeps its resources; an image in the clipboard is never read as text' (#276) from fix/x-resources-and-clipboard-images into main

This commit is contained in:
2026-10-04 14:34:16 +00:00
6 changed files with 43 additions and 4 deletions
+13
View File
@@ -67,3 +67,16 @@ would empty whenever the runtime restarted.
them, `mctl` reads them as unknown.
- `CM_*` reach the session through `node-env` (ADR 0203), so the account's environment module must
be assigned too. Without it clipmenu runs on its defaults: both selections, 1000 clips, 8 lines.
## Only text is read (added 2026-10-04)
clipmenud reads a selection with `timeout 1 xsel -o`. A selection holding an image, such as a
screenshot copied as `image/png`, arrives in pieces. One second is too short for megabytes, so
`timeout` kills xsel half way, and the program owning the image waits for ever for a reader that is
gone. From then on every paste hangs, and an app asking on its main thread (Electron: Slack) freezes.
The first screenshot after this module was assigned did exactly that.
So clipmenud runs with the module's own `xsel` first on its `PATH`
(`/usr/local/lib/mesh-clipmenu/xsel`). Before a read it asks the selection for its `TARGETS` and goes
ahead only when the selection offers text. It uses `xclip` for that question, which the `xclip`
module installs on every workstation.
@@ -33,7 +33,9 @@ func TestTheDaemonStartsOnceFromTheSessionsStart(t *testing.T) {
t.Fatalf("%+v", m.Shell)
}
code := m.Shell[0].Code
if strings.Count(code, "\nclipmenud &\n") != 1 || strings.Contains(code, "greenclip") {
// Started once, through the module's text-only xsel (a selection holding an image is never read).
if strings.Count(code, "clipmenud &\n") != 1 || !strings.Contains(code, `PATH="/usr/local/lib/mesh-clipmenu:$PATH" clipmenud &`) ||
strings.Contains(code, "greenclip") {
t.Fatalf("%q", code)
}
for _, r := range m.Resources {
+8 -1
View File
@@ -33,7 +33,7 @@
{
"for": "xinitrc",
"slot": "normal",
"code": "# The clipboard's history (module clipmenu, novox/hq ADR 0208): clipmenud collects every copy from\n# here on, once per session. It keeps the history in the account's runtime directory, so a reboot\n# forgets it, and with it every password that was ever copied.\nclipmenud &\n"
"code": "# The clipboard's history (module clipmenu, novox/hq ADR 0208): clipmenud collects every copy from\n# here on, once per session. It keeps the history in the account's runtime directory, so a reboot\n# forgets it, and with it every password that was ever copied.\n# Through the module's xsel, which reads only a selection offering text (see the README).\nPATH=\"/usr/local/lib/mesh-clipmenu:$PATH\" clipmenud &\n"
}
],
"resources": [
@@ -55,6 +55,13 @@
"owner": "${machine:account}",
"mode": "0644",
"content": "# The clipboard's history key (module clipmenu, novox/hq ADR 0208). Owned by the mesh: replaced at\n# every push. clipmenu shows the history through `dmenu`, the node's dmenu-compatible command, which\n# the holder of node-launcher answers (rofi on the workstations); the chosen entry is put back on the\n# clipboard.\nbindsym $mod+period exec --no-startup-id clipmenu -p Clipboard\n"
},
{
"id": "text-only",
"type": "file",
"path": "/usr/local/lib/mesh-clipmenu/xsel",
"mode": "0755",
"content": "#!/bin/sh\n# xsel as clipmenud sees it, written by the mesh (module clipmenu). Replaced at every push.\n#\n# clipmenud records text, and reads a selection with `timeout 1 xsel -o`. A selection holding an\n# image (a screenshot copied as image/png) is sent in pieces; one second is too short for megabytes,\n# timeout kills xsel half way, and the program owning the image waits for ever for a reader that is\n# gone. From then on nothing can ask the clipboard anything: pastes hang, and an Electron app that\n# asks on its main thread freezes. So a read goes ahead only when the selection offers text.\ncase \" $* \" in\n*\" -o \"* | *\" --output \"*)\n\tselection=clipboard\n\tcase \" $* \" in\n\t*\" --primary \"* | *\" -p \"*) selection=primary ;;\n\t*\" --secondary \"* | *\" -s \"*) selection=secondary ;;\n\tesac\n\ttargets=$(timeout 1 xclip -selection \"$selection\" -t TARGETS -o 2>/dev/null) || exit 1\n\tprintf '%s\\n' \"$targets\" | grep -qxE 'UTF8_STRING|STRING|TEXT|text/plain(;charset=utf-8)?' || exit 1\n\t;;\nesac\nexec /usr/bin/xsel \"$@\"\n"
}
],
"build": {
+9
View File
@@ -100,3 +100,12 @@ to-be 42's order (`xorg`, `lemurs`, `i3` in one sitting), this cannot happen.
- **The package swap is a person's act** (step 1). The host's package resource cannot replace a
package that provides the same name.
- **No restart, by design.** A configuration change takes a reboot to show.
## The X server never resets (added 2026-10-04)
lemurs starts the server through `/etc/lemurs/xserver`, which adds `-noreset`. Without it, X resets
whenever its last client disconnects, and a reset empties the resource database. The session's start
merges the X resources before any long-lived client connects. On the first login with these modules,
the server reset twice within four seconds: the X log showed three server generations. xterm fell
back to the bitmap `fixed` font, without the mesh's face and colours. lemurs reads its configuration
when it starts, so the change applies after the next boot.
+2 -1
View File
@@ -342,7 +342,8 @@ x11_display = ":1"
xserver_timeout_secs = 60
# Where to find the X11 server binary
xserver_path = "/usr/bin/X"
# mesh: the server through the wrapper below, which adds -noreset.
xserver_path = "/etc/lemurs/xserver"
# Where to find the X11 xauth binary
xauth_path = "/usr/bin/xauth"
File diff suppressed because one or more lines are too long