Skip to content

Instantly share code, notes, and snippets.

@gerwang
Last active March 2, 2026 16:04
Show Gist options
  • Select an option

  • Save gerwang/7d9eea0bbdc4a3a966f582769a95019e to your computer and use it in GitHub Desktop.

Select an option

Save gerwang/7d9eea0bbdc4a3a966f582769a95019e to your computer and use it in GitHub Desktop.
Wayscriber wrapper: map Hyprland workspace to board page and auto-create missing pages (supports compressed sessions)

Wayscriber Workspace-Page Wrapper (Hyprland)

This wrapper maps the current Hyprland workspace to a Wayscriber page on the active board.

What it does

  • Reads the current workspace number from hyprctl -j activeworkspace
  • Resolves the Wayscriber session file via wayscriber --session-info
  • Ensures the active board has enough pages (auto-creates empty pages as needed)
  • Sets active_page = workspace - 1
  • Opens/toggles Wayscriber:
    • If daemon is running: pkill -SIGUSR1 wayscriber
    • Otherwise: starts one-shot wayscriber --active

Requirements

  • hyprctl
  • wayscriber
  • jq
  • gh (only needed to publish this gist)

Install

install -Dm755 wayscriber-workspace-page.sh ~/.local/bin/wayscriber-workspace-page

Hyprland bind

bind = SUPER, D, exec, /home/$USER/.local/bin/wayscriber-workspace-page

Then reload Hyprland:

hyprctl reload

Optional dry run (session sync only)

WAYSCRIBER_SYNC_ONLY=1 ~/.local/bin/wayscriber-workspace-page

Notes

  • Workspace N maps to page index N-1.
  • If Wayscriber changes session schema in future versions, verify the jq update logic.

Compressed session files

Newer Wayscriber versions may save session snapshots as gzip-compressed .json files. This wrapper supports both plain JSON and gzip-compressed session files.

#!/usr/bin/env bash
set -euo pipefail
log() {
printf 'wayscriber-workspace-page: %s\n' "$*" >&2
}
get_workspace_number() {
local ws_json ws_id ws_name
ws_json="$(hyprctl -j activeworkspace 2>/dev/null || true)"
if [[ -z "$ws_json" ]]; then
return 1
fi
ws_id="$(jq -r '.id // empty' <<<"$ws_json")"
if [[ "$ws_id" =~ ^[0-9]+$ ]] && (( ws_id > 0 )); then
printf '%s\n' "$ws_id"
return 0
fi
ws_name="$(jq -r '.name // empty' <<<"$ws_json")"
if [[ "$ws_name" =~ ^[0-9]+$ ]] && (( ws_name > 0 )); then
printf '%s\n' "$ws_name"
return 0
fi
return 1
}
get_session_file() {
local info
info="$(wayscriber --session-info 2>/dev/null || true)"
printf '%s\n' "$info" \
| sed -n 's/^ Session file[[:space:]]*:[[:space:]]*//p' \
| head -n 1
}
sync_session_page() {
local session_file target_page target_index active_board tmp_file compressed tmp_out
session_file="$1"
target_page="$2"
target_index=$((target_page - 1))
if [[ ! -f "$session_file" ]]; then
log "session file not found ($session_file), skipping page sync"
return 0
fi
if gzip -t "$session_file" >/dev/null 2>&1; then
compressed=1
else
compressed=0
fi
if (( compressed )); then
active_board="$(gzip -dc "$session_file" | jq -r '.active_board_id // empty' 2>/dev/null || true)"
else
active_board="$(jq -r '.active_board_id // empty' "$session_file" 2>/dev/null || true)"
fi
if [[ -z "$active_board" || "$active_board" == "null" ]]; then
if (( compressed )); then
active_board="$(gzip -dc "$session_file" | jq -r '.boards[0].id // "transparent"' 2>/dev/null || printf 'transparent')"
else
active_board="$(jq -r '.boards[0].id // "transparent"' "$session_file" 2>/dev/null || printf 'transparent')"
fi
fi
tmp_file="$(mktemp "${session_file}.tmp.XXXXXX")"
if (( compressed )); then
if ! gzip -dc "$session_file" | jq \
--arg board_id "$active_board" \
--argjson target "$target_index" \
'
def empty_page: {shapes: [], undo_stack: [], redo_stack: []};
.boards = (.boards // [])
| if ((.boards | map(.id) | index($board_id)) == null) then
.boards += [{id: $board_id, pages: [empty_page], active_page: 0}]
else
.
end
| (.boards[] | select(.id == $board_id)) |= (
.pages = (.pages // [])
| if (.pages | length) == 0 then .pages = [empty_page] else . end
| .pages += [range((.pages | length); ($target + 1)) | empty_page]
| .active_page = $target
)
| .active_board_id = $board_id
' > "$tmp_file"; then
rm -f "$tmp_file"
log "failed to update session JSON ($session_file), skipping page sync"
return 0
fi
else
if ! jq \
--arg board_id "$active_board" \
--argjson target "$target_index" \
'
def empty_page: {shapes: [], undo_stack: [], redo_stack: []};
.boards = (.boards // [])
| if ((.boards | map(.id) | index($board_id)) == null) then
.boards += [{id: $board_id, pages: [empty_page], active_page: 0}]
else
.
end
| (.boards[] | select(.id == $board_id)) |= (
.pages = (.pages // [])
| if (.pages | length) == 0 then .pages = [empty_page] else . end
| .pages += [range((.pages | length); ($target + 1)) | empty_page]
| .active_page = $target
)
| .active_board_id = $board_id
' "$session_file" > "$tmp_file"; then
rm -f "$tmp_file"
log "failed to update session JSON ($session_file), skipping page sync"
return 0
fi
fi
if (( compressed )); then
tmp_out="$(mktemp "${session_file}.tmp.XXXXXX")"
if ! gzip -n -c "$tmp_file" > "$tmp_out"; then
rm -f "$tmp_file" "$tmp_out"
log "failed to re-compress session JSON ($session_file), skipping page sync"
return 0
fi
rm -f "$tmp_file"
mv "$tmp_out" "$session_file"
else
mv "$tmp_file" "$session_file"
fi
}
launch_wayscriber() {
if pgrep -af 'wayscriber --daemon' >/dev/null 2>&1; then
pkill -SIGUSR1 wayscriber
else
if ! setsid -f wayscriber --active >/dev/null 2>&1; then
wayscriber --active >/dev/null 2>&1 &
fi
fi
}
main() {
local workspace session_file
if ! workspace="$(get_workspace_number)"; then
log "could not read active Hyprland workspace, launching without page sync"
launch_wayscriber
return 0
fi
session_file="$(get_session_file)"
if [[ -n "$session_file" ]]; then
sync_session_page "$session_file" "$workspace"
else
log "could not resolve Wayscriber session file, launching without page sync"
fi
if [[ "${WAYSCRIBER_SYNC_ONLY:-0}" == "1" ]]; then
return 0
fi
launch_wayscriber
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment