Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased]

### Platforms

- Fixed source installs failing on a fresh dependency resolution

## 0.3.0 (2026-09-03)

### Platforms
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ iced = { version = "0.14", default-features = false, features = [
] }
iced_layershell = "0.19.1"
iced_runtime = "0.14"
# pinned: iced_exdevtools 0.19.1 (unconditional dep of iced_layershell 0.19.1)
# fails to compile against winit-core 0.31.0-beta.3; remove once iced_layershell
# ships a release on the fixed iced_exdevtools 0.20 line
winit-core = "=0.31.0-beta.2"
tokio = { version = "1", features = ["rt", "net", "io-util", "sync", "time", "macros"] }
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
futures-channel = { version = "0.3", default-features = false, features = ["std"] }
Expand Down Expand Up @@ -64,6 +68,7 @@ hyprlay-core = { workspace = true }
clap = { workspace = true }
iced = { workspace = true }
iced_runtime = { workspace = true }
winit-core = { workspace = true }
tokio = { workspace = true }
futures-util = { workspace = true }
futures-channel = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Download the latest binaries from [releases page](https://github.com/FAZuH/hyprl

Or install with [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html):
```sh
cargo install --git https://github.com/FAZuH/hyprlay
cargo install --locked --git https://github.com/FAZuH/hyprlay

# Or build from source:
cargo build --release
Expand Down
30 changes: 1 addition & 29 deletions dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Development helper script
# Usage: ./dev.sh [command1] [command2] ...
# commands: format | lint | test | docs | demo | all | help
# commands: format | lint | test | docs | all | help
# plus any commands provided by modules (scripts/dev-*.sh, dev/*.sh, dev-*.sh)
# Multiple commands can be specified and will execute left to right

Expand Down Expand Up @@ -101,33 +101,6 @@ cmd_docs() {
}
dev_desc docs "Compile Mermaid diagrams to images"

cmd_demo() {
inf "Building release binary..."
cargo build --release
scs "Release build completed"

inf "Creating wrapper script..."
local wrapper_dir="/tmp/tomo-demo-bin"
mkdir -p "$wrapper_dir"
cat > "$wrapper_dir/tomo" << SCRIPT
#!/bin/bash
exec $PWD/target/release/tomo --config-path /tmp/tomo-demo "\$@"
SCRIPT
chmod +x "$wrapper_dir/tomo"
export PATH="$wrapper_dir:$PATH"
trap "rm -rf $wrapper_dir" EXIT
scs "Wrapper created at $wrapper_dir/tomo"

if ! command -v vhs &> /dev/null; then
wrn "vhs not found. Install it: https://github.com/charmbracelet/vhs"
fi

inf "Running demo tape..."
vhs scripts/demo.tape
scs "Demo tape completed"
}
dev_desc demo "Build release, alias, and run vhs demo tape"

cmd_all() {
inf "Running all tasks..."
cmd_format
Expand Down Expand Up @@ -180,7 +153,6 @@ Examples:
./dev.sh lint # Run linter
./dev.sh test # Run tests
./dev.sh docs # Compile Mermaid diagrams
./dev.sh demo # Build release, alias, and run demo tape
./dev.sh format lint # Format then lint
./dev.sh all # Run format, lint, and test

Expand Down
65 changes: 55 additions & 10 deletions docs/dev/debug-probes.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,80 @@
# Debug probes

Two throwaway-style probes dump raw Discord RPC traffic. They do not
Three probes live here. Two dump raw Discord RPC traffic; they do not
import any crate code — they speak the wire protocols directly, so they
keep working (and stay truthful) even when the adapters change.
keep working (and stay truthful) even when the adapters change. The
third is a plain shell sampler for RSS-hunting on a process that does
not look like a leak in the code but does in btop.

They live in their own mini-crate under `scripts/`, deliberately separate
from the main package, so they never build with or get installed by
`hyprlay`. Their targets are examples, not bins: that keeps the repo's
`cargo install --git` scan at exactly one binary package, so bare
installs work. Run them from that directory:
The two Discord probes live in their own mini-crate under `scripts/`,
deliberately separate from the main package, so they never build with or
get installed by `hyprlay`. Their targets are examples, not bins: that
keeps the repo's `cargo install --git` scan at exactly one binary
package, so bare installs work. Run them from that directory:

```sh
cd scripts
cargo run --example ipcprobe # unix-socket IPC probe (current transport)
cargo run --example wsprobe # historical websocket bridge probe
```

## Daemon memory sampler (`rssprobe.sh`)

`scripts/rssprobe.sh` samples one process's memory every couple of
seconds and appends one CSV row per interval:
`epoch,timestamp,rss_kb,hwm_kb,note`. It reads `/proc/<pid>/status`
directly — VmRSS plus the peak VmHWM — so every row shows both the
live footprint and the historical high-water mark. That pair is the
diagnosis signal:

- **RSS at or near HWM** — the process is at its historical peak;
what you are watching is real growth.
- **RSS well below HWM** — memory was freed but not returned to the
OS (allocator retention). The interesting question becomes *what
spiked it earlier*, not *what is growing now*.

Usage — sampler in one terminal, markers from another while you
work:

```sh
scripts/rssprobe.sh # sample `pidof hyprlayd` every 2s
scripts/rssprobe.sh -p <pid> -i 5 -o d.csv # explicit target/interval/output
scripts/rssprobe.sh mark "VC join" # append a labeled marker row
scripts/rssprobe.sh mark -o d.csv "VC join" # marker into the same CSV
```

Ctrl-C stops the sampler. Both commands append to the same CSV
(`rssprobe.csv` in the CWD by default; the header is written once),
so a later plot or diff shows exactly which scenario moved the
needle.

Runbook for the hyprlayd memory regression (spec: v031 memory, H1–H5):

1. Cold idle: start the sampler on a freshly restarted daemon, mark
`idle-start`, wait 10 min, mark `idle-end`.
2. H1 bait: mark `window-move-storm`, sweep a window across the
overlay with `dim-on-hover` on (the poll path opens a fresh
Hyprland socket per 50ms tick while connected with a non-empty
roster), then repeat with it off.
3. VC join/leave cycles, camera/stream toggles: mark each; watch RSS
vs HWM after the leave.
4. Repeat the identical series on the v0.3.0 release build (`hyprlayd`
from that tag) and diff the CSVs.

## Current transport — local IPC (`ipcprobe`)

The daemon talks to Discord over Discord's local unix socket at
`$XDG_RUNTIME_DIR/discord-ipc-N`; the client side lives in
`src/adapters/ipc.rs`. The wire format is 8-byte little-endian framing
(opcode u32 + payload length u32) carrying JSON payloads.
`src/daemon/adapters/ipc.rs`. The wire format is 8-byte little-endian
framing (opcode u32 + payload length u32) carrying JSON payloads.

The probe connects to the socket, does the same framing, sends
`HANDSHAKE` then `AUTHORIZE`, and prints every frame for 60s. Use it to
see what a stock Discord client answers and how it frames data.

Start here when Discord changes something: confirm at the protocol level
whether opcodes or payloads moved before touching
`src/adapters/discord.rs`.
`src/daemon/adapters/discord.rs`.

The socket has no HTTP layer, so there is no origin validation — any
properly registered application id connects with zero portal
Expand Down
90 changes: 90 additions & 0 deletions scripts/rssprobe.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# rssprobe.sh — sample one process's memory to CSV for RSS-hunting.
#
# The sampler writes one CSV row per interval: epoch, ISO-8601 timestamp,
# VmRSS, and VmHWM (peak) in kB. RSS below HWM means memory was freed but
# not returned to the OS (allocator retention); RSS climbing toward or
# past the old HWM means real growth. Scenario markers are rows whose
# note column carries the label, so a plot or a diff shows exactly which
# scenario moved the needle.
#
# Usage:
# scripts/rssprobe.sh # sample `pidof hyprlayd` every 2s
# scripts/rssprobe.sh -p PID -i 2 -o f.csv
# scripts/rssprobe.sh mark "VC join" # append a marker row (other shell)
# scripts/rssprobe.sh mark # marker with no label ("scenario")
#
# Ctrl-C stops the sampler. Both commands append to the same CSV (header
# written once), so run the sampler in one terminal and mark scenarios
# from another while you work.

set -euo pipefail

usage() {
grep '^# ' "$0" | sed 's/^# //'
exit "${1:-0}"
}

csv=rssprobe.csv
pid=""
interval=2

cmd=${1:-sample}
case $cmd in
mark)
shift
label=""
while [ $# -gt 0 ]; do
case $1 in
-o) csv=$2; shift 2 ;;
-i) shift 2 ;; # tolerated for copy-paste symmetry, unused here
*) label="$label${label:+ }$1"; shift ;;
esac
done
[ -n "$label" ] || label=scenario
[ -f "$csv" ] || echo "epoch,timestamp,rss_kb,hwm_kb,note" > "$csv"
printf '%s,%s,,,%s\n' "$(date +%s)" "$(date +%FT%T)" "$label" >> "$csv"
exit 0
;;
sample)
shift || true
;;
-p | -i | -o | --help)
;;
*)
usage 2
;;
esac

while [ $# -gt 0 ]; do
case $1 in
-p) pid=$2; shift 2 ;;
-i) interval=$2; shift 2 ;;
-o) csv=$2; shift 2 ;;
--help) usage ;;
*) usage 2 ;;
esac
done

if [ -z "$pid" ]; then
pid=$(pidof hyprlayd 2>/dev/null | tr ' ' '\n' | head -1) || {
echo "rssprobe: no hyprlayd running; pass -p PID" >&2
exit 1
}
fi
if [ "$(pidof hyprlayd 2>/dev/null | wc -w)" -gt 1 ] && [ "$pid" = "$(pidof hyprlayd | tr ' ' '\n' | head -1)" ]; then
echo "rssprobe: multiple hyprlayd processes; sampling $pid (first). Pass -p to pick." >&2
fi

status=/proc/$pid/status
[ -r "$status" ] || { echo "rssprobe: cannot read $status" >&2; exit 1; }

[ -f "$csv" ] || echo "epoch,timestamp,rss_kb,hwm_kb,note" > "$csv"
echo "rssprobe: sampling pid $pid every ${interval}s -> $csv (Ctrl-C to stop)" >&2

while :; do
rss=$(awk '/^VmRSS:/{print $2}' "$status") || exit 0 # process gone
hwm=$(awk '/^VmHWM:/{print $2}' "$status")
printf '%s,%s,%s,%s,\n' "$(date +%s)" "$(date +%FT%T)" "$rss" "$hwm" >> "$csv"
sleep "$interval"
done
Loading