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
20 changes: 12 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@ jobs:
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Windows PowerShell display policy and journal
# The implementation now belongs to Remote Desktops. Keep the required
# native policy check on its immutable, validated extraction revision.
- uses: actions/checkout@v4
with:
repository: jdvmi00/remote-desktops
ref: 30d795c83164f812ec70a4374e68926e1cb39d9c
path: remote-desktops
persist-credentials: false
- name: Migrated Windows PowerShell display policy and journal
shell: powershell
run: .\stream\windows\Test.ps1 -PolicyOnly
run: .\remote-desktops\remote_desktops\windows\Test.ps1 -PolicyOnly
test:
runs-on: ubuntu-latest
timeout-minutes: 20
Expand All @@ -29,7 +37,7 @@ jobs:
- name: Scripts
run: shellcheck install.sh uninstall.sh
- name: Development helper
run: python3 test/dev.py
run: python3 test/dev.py && python3 test/upgrade.py
- name: Engine
run: lua test/harness.lua
- name: Window navigation
Expand All @@ -38,12 +46,8 @@ jobs:
run: lua test/bridge.lua
- name: Session recovery
run: python3 test/session.py && lua test/session.lua
- name: Remote streams
run: python3 test/stream.py && python3 test/windows_display.py && lua test/stream.lua
- name: Scenes and content
run: python3 test/scenes.py && python3 test/apps.py && lua test/scenes.lua && node test/content.js
- name: Stream quality and reconnect
run: python3 test/quality.py && lua test/quality.lua
run: python3 test/scenes.py && python3 test/apps.py && lua test/scenes.lua && lua test/swap.lua && node test/content.js
- name: Managed layout browsing
run: python3 test/browse.py && node test/browse.js
- name: Overlay geometry
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

- Remote connections now belong to Remote Desktops. Remove Hypertile's legacy
controller, host adapters, display scripts, and remote controls. Scenes and
session recovery use ordinary installed apps; pinned swaps remain generic.
Upgrade checks preserve unresolved host recovery and user configuration.

- Session recovery: batched automatic checkpoints with durable publication and
previous generations; named sessions; protected partial restores; supported
app relaunch; workspace/layout, native window order, pins, sizing, floating
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,10 @@ not own remote connections or host display settings.
Use the overlay’s **Scenes** tab or `hypertile-ctl scene` to assign apps, local
windows, or Empty, then save the arrangement. See [scenes and content](docs/SCENES.md)
for setup, migration from legacy stream sources, and recovery behavior.
The [legacy stream tools](docs/STREAMS.md) remain available to restore existing
host journals while migrating to Remote Desktops.
Remote connections and host recovery now belong to
[Remote Desktops](https://github.com/jdvmi00/remote-desktops). Upgrade checks
require legacy connections to be disconnected and restored before removing
their old runtime files; saved configuration and journals are preserved.

## License

Expand Down
10 changes: 2 additions & 8 deletions bin/hypertile-ctl
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,8 @@ JSON exchanged with editors: {"name": "...", "spec": {...}}
monitor size, reserved edges, gaps, and layout area
workspaces [--json] every workspace with its monitor and layout
windows [--json] open windows (class, title, workspace)
computers [--json] configured remote computers and pairing references
scene <command> [...] list, save, apply, current, restore, retry, content;
see docs/SCENES.md
stream <command> [...] probe, connect, focus, disconnect, status, retry,
restore, release, swap, reconnect, local, quality,
measure, readability; see docs/STREAMS.md
session <command> [name] status, save <name>, restore [name], freeze, resume,
stop, logout, reboot, shutdown; see docs/SESSIONS.md
default [name|BUILTINS] [--no-reload]
Expand Down Expand Up @@ -448,16 +444,14 @@ end

local argv = { ... }
local cmd = table.remove(argv, 1)
if cmd == "stream" or cmd == "computers" or cmd == "scene" then
local entry = cmd == "scene" and "hypertile-scenes" or "hypertile-stream"
if cmd == "scene" then
local entry = "hypertile-scenes"
local command = src and src ~= "" and (src .. "/bin/" .. entry)
or ((os.getenv("HOME") or "") .. "/.local/bin/" .. entry)
local words = { command }
if cmd == "computers" then words[#words + 1] = "computers" end
if cmd == "scene" then
if #argv == 0 then argv[1] = "current" end
end
if cmd == "stream" and #argv == 0 then argv[1] = "status" end
for _, value in ipairs(argv) do words[#words + 1] = value end
for i, value in ipairs(words) do words[i] = "'" .. value:gsub("'", "'\\''") .. "'" end
local ok, _, code = os.execute(table.concat(words, " "))
Expand Down
4 changes: 2 additions & 2 deletions bin/hypertile-scenes
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import sys
root = Path(os.environ["HYPERTILE_SRC"]) if os.environ.get("HYPERTILE_SRC") else Path(
os.environ.get("XDG_DATA_HOME") or Path.home() / ".local/share") / "hypertile"
sys.path.insert(0, str(root / "session"))
sys.path.insert(0, str(root / "stream"))
runpy.run_path(str(root / "stream/scene_service.py"), run_name="__main__")
sys.path.insert(0, str(root / "scenes"))
runpy.run_path(str(root / "scenes/scene_service.py"), run_name="__main__")
12 changes: 0 additions & 12 deletions bin/hypertile-stream

This file was deleted.

22 changes: 15 additions & 7 deletions dev
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import tempfile
import time

ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(ROOT / "session"))
from upgrade import check_legacy, obsolete, cleanup
CONFIG = Path(os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config")
DATA = Path(os.environ.get("XDG_DATA_HOME") or Path.home() / ".local/share")
STATE = Path(os.environ.get("XDG_STATE_HOME") or Path.home() / ".local/state") / "hypertile"
Expand Down Expand Up @@ -56,9 +58,8 @@ def groups():
return {
"lua": sorted(ROOT.glob("hypertile*.lua")),
"cli": [ROOT / "bin/hypertile-ctl"],
"session": [ROOT / "bin/hypertile-session", *sorted((ROOT / "bin").glob("hypertile-stream")), *sorted((ROOT / "bin").glob("hypertile-scenes")),
*sorted((ROOT / "session").glob("*.py")), *sorted((ROOT / "stream").glob("*.py")),
*sorted((ROOT / "stream/windows").glob("*.ps1")), *sorted((ROOT / "stream/windows").glob("*.cs"))],
"session": [ROOT / "bin/hypertile-session", *sorted((ROOT / "bin").glob("hypertile-scenes")),
*sorted((ROOT / "session").glob("*.py")), *sorted((ROOT / "scenes").glob("*.py"))],
"shell": [ROOT / "manifest.json", *sorted(p for p in (ROOT / "plugin").rglob("*") if p.is_file())],
}

Expand All @@ -73,7 +74,7 @@ def destination(path):
relative = path.relative_to(ROOT)
if relative.parts[0] == "bin":
return BIN / path.name
if relative.parts[0] in ("session", "stream"):
if relative.parts[0] in ("session", "scenes"):
return DATA / "hypertile" / relative
return CONFIG / "hypr" / path.name

Expand All @@ -94,8 +95,7 @@ def validate(sources):
lua = sources["lua"] + sources["cli"] + sorted((ROOT / "layouts").glob("*.lua"))
run("lua", "-", *lua, input="for _, path in ipairs(arg) do assert(loadfile(path)) end\n")
for path in sources["session"] + [ROOT / "dev"]:
if path.suffix not in (".ps1", ".cs"):
compile(path.read_bytes(), str(path), "exec")
compile(path.read_bytes(), str(path), "exec")
run("omarchy", "plugin", "validate", ROOT)
qmlformat = shutil.which("qmlformat") or "/usr/lib/qt6/bin/qmlformat"
if Path(qmlformat).is_file():
Expand Down Expand Up @@ -182,7 +182,7 @@ def stopped_session():
run(BIN / "hypertile-scenes", "stop", timeout=10)
wait_for(scene_available, "scene service to stop")
stream_lock = None
if (BIN / "hypertile-stream").exists():
if (BIN / "hypertile-stream").exists() or (STATE / "streams/state.json").exists():
path = STATE / "streams/writer.lock"
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
stream_lock = stack.enter_context(path.open("a"))
Expand All @@ -202,6 +202,7 @@ def stopped_session():
raise RuntimeError("disconnect/restore legacy Hypertile streams before applying runtime changes")
run(BIN / "hypertile-stream", "stop", timeout=55)
wait_for(stream_available, "stream controller to stop")
check_legacy(STATE)
path = STATE / "sessions/writer.lock"
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
with path.open("a") as lock:
Expand Down Expand Up @@ -298,6 +299,13 @@ def apply(force):
saved.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(target, saved)
atomic_write(target, content, 0o755 if path.relative_to(ROOT).parts[0] == "bin" else 0o644)
if "session" in changed:
for target in obsolete(BIN, DATA):
if target.exists():
saved = backup / "retired" / (Path("bin") / target.name if target.parent == BIN else target.relative_to(DATA))
saved.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(target, saved)
cleanup(BIN, DATA)
if "lua" in changed:
reload_lua()
finally:
Expand Down
3 changes: 3 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ remote-stream and scene work is not part of this candidate.
3. Push the feature branch and open a PR targeting `develop`.
4. Wait for `test` and `windows-display-policy` to pass, then merge the PR.

The `windows-display-policy` job runs the extracted policy suite from an immutable
Remote Desktops commit on Windows; host code is no longer duplicated here.

Both integration branches require passing checks on an up-to-date PR; no
additional reviewer is required for this solo-maintainer repository. Force
pushes and deletion are disabled. The extra lock on `main` prevents even a
Expand Down
28 changes: 16 additions & 12 deletions docs/SCENES.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,16 @@ Existing `type: "stream"` scene files and legacy host recovery journals are
preserved. They are not automatically converted: install each computer's
Remote Desktops launcher, disconnect/restore legacy Hypertile streams, and
replace those sources with `type: "app"` entries. Legacy stream scenes appear
invalid until migrated. A live legacy stream on a workspace blocks new scene
changes there. Runtime installation also requires disconnecting/restoring legacy
sources first, so replacing the old service cannot orphan their recovery.

The old `hypertile-stream` CLI remains available for legacy connection recovery;
it is no longer auto-started by the layout loader and its daemon does not apply
old scenes. `hypertile-stream scene ...` forwards to the independent service.
Legacy stream profile changes require disconnecting and reconnecting with
`--profile`. See [legacy stream recovery](STREAMS.md) for preserved journals and
restoration tools. The old stream-specific overlay controls are not offered by
the new app catalog; connection controls belong to Remote Desktops.
invalid until migrated. Upgrade and uninstall refuse to remove recovery tools
while any legacy connection is desired or has a pending host journal, including
when its controller is stopped. Finish recovery with the previously installed
version before upgrading. Configuration and state files are kept.

Hypertile no longer installs `hypertile-stream`, host adapters, Windows display
scripts, connection controls, or quality measurements. The standalone Scenes
service lives under `scenes/`; it only manages layouts and app placement.
Connection, input capture, audio, and host display controls belong to
[Remote Desktops](https://github.com/jdvmi00/remote-desktops).

## Validation

Expand All @@ -171,4 +170,9 @@ during the check and resumed afterward. The installed plugin was not replaced.
Automated tests cover exact/ambiguous matches, interrupted launches and placement
replies, app-only session recovery, cancelled/superseded operations, XDG launcher
precedence, socket/lock isolation, and preservation of manual moves/closes.
The Windows laptop has not been validated live through this integration.
Both MacBook and the Windows work laptop were subsequently validated live with
Remote Desktops launchers and the saved two-app scene. Workspace movement,
launcher reuse, explicit scene reapplication, and Windows reconnection passed;
the owner confirmed video, mouse, and keyboard on both computers. These checks
preceded removal of the legacy code; removal is covered by the generic app,
scene, session, swap, preview, and upgrade regression suites.
11 changes: 7 additions & 4 deletions docs/SESSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ service during recovery in the same compositor does not relaunch already
attempted apps. Automatic saving resumes only after all saved windows have
matched and settled, or after an explicit `resume`.

Workspace [scenes](SCENES.md) are checkpointed as versioned definitions alongside
stream references. The stream controller restores their content after the local
workspace returns; it retains disconnect decisions independently of older
checkpoints. Scene definitions do not include transient compositor window IDs.
Workspace [scenes](SCENES.md) are checkpointed as versioned definitions. The
independent scene service places their apps, including on an otherwise empty
workspace. Moved or closed app assignments are omitted from recovery without
changing saved scene defaults. Normal app recovery handles moved windows using
their exact launcher identity. Old remote assignments generate a migration
message and are never sent to a connection controller. Scene definitions do not
include transient compositor window IDs.
Loading
Loading