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
30 changes: 28 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,32 @@ name: tests

on:
push:
branches: [main]
branches: [main, develop]
pull_request:

permissions:
contents: read

jobs:
windows-display-policy:
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
# 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: .\remote-desktops\remote_desktops\windows\Test.ps1 -PolicyOnly
test:
runs-on: ubuntu-latest
timeout-minutes: 20
container: archlinux:latest
steps:
# The engine targets the Lua that Hyprland embeds (5.5); Arch tracks it.
Expand All @@ -17,13 +37,19 @@ 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
run: lua test/navigation.lua
- name: Bridge and CLI
run: lua test/bridge.lua
- name: Session recovery
run: python3 test/session.py && lua test/session.lua
- name: Scenes and content
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
run: node test/geometry.js
- name: Overlay editor
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ __pycache__/
# Private planning notes, kept locally only
docs/PLAN.md
docs/REMOTE-SOURCES.md
docs/COOPERATING-AGENTS.md
docs/REMOTE-SOURCES-PROTOTYPES.md
41 changes: 41 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Repository workflow

Read [docs/RELEASING.md](docs/RELEASING.md) before changing branches, CI,
release metadata, tags, GitHub settings, or marketplace submissions.

## Branches and preservation

- `main` is the default, stable installation branch. It is frozen at
`1ba0f8a30526f2148ff46885d67991862ae90f00` for marketplace issue #4893.
Do not push, merge, rebase, reset remotely, or add even documentation to it.
- Work on feature branches based on `develop`; target pull requests at `develop`.
Before editing, inspect `git status` and preserve existing uncommitted work.
Stage only your changes; never discard or commit unrelated user changes.
- Never force-push `main` or `develop`, move published tags, change the default
branch, disable protection, or bypass CI as a routine fix.
- Moving `main` or removing its freeze requires an explicit release instruction
from the owner. Ordinary implementation requests authorize development work,
not a release. Keep `main` locked between releases and throughout review.

## CI and delivery

- `.github/workflows/test.yml` defines the test commands. Keep CI running on
pushes to `develop` and `main`, and on pull requests.
- Run the relevant suites locally. Before merging, both GitHub checks `test`
and `windows-display-policy` must pass on the current PR revision. Fix
failures without weakening or skipping checks. Do not claim an unavailable
local platform check passed; use its GitHub runner.
- Local runtime installation (`./dev apply`, `./install.sh`) is separate from CI
and release publication. Do not run these merely to test a workflow change.
- Development CI must never automatically push to `main`, publish a release,
edit a marketplace issue, or install the plugin on the user's desktop.
- For an explicitly requested release, follow docs/RELEASING.md: prepare all
content and version changes first, promote once, record the resulting full
SHA, immediately freeze `main`, and obtain marketplace reports for that SHA.
A successful GitHub test run is not marketplace approval.
- After approval, keep developing on `develop`. A new `main` commit needs the
marketplace update process; an existing approval does not cover it.

This file lives on `develop` because adding it to the currently frozen release
would change the exact commit the owner selected. GitHub protection enforces
the freeze independently of this file.
32 changes: 31 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
# Changelog

## Unreleased
## 1.1.0 (2026-09-06)

- Scenes: save a workspace's layout together with what each zone holds: an
installed app, one open window, local windows in fill order, or Empty.
Applying a scene launches or reuses each app and places it once; you can
then move or close it freely. Scenes live in `~/.config/hypertile/scenes/`
and are managed from the overlay's Scenes tab or `hypertile-ctl scene`; an
independent `hypertile-scenes` service owns placement. See
[scenes and content](docs/SCENES.md).

- Scenes tab redesign. The header names the workspace until a scene is
applied, then the scene with how many apps are placed. Saved scenes are
cards like the layout list, with the apps drawn in their zones; the row
applies. One zone list replaces CONTENT, ZONE and CHANGE TO; the picker
under the selected zone filters as you type, shows app icons, and groups
open windows (with titles), remote desktops, and installed apps. Zone
cards show what they hold with icon, name, and state, plus Change… and
Clear; a card that needs attention is outlined and offers Retry. Digits
select zones by fill number, hovering a match previews it in the card, and
zones are listed by position ("Top left"). The catalog lists each app's
icon and each saved scene's sources, and skips placeholder window classes.

- Remote desktops: each computer's launcher from
[Remote Desktops](https://github.com/jdvmi00/remote-desktops) is an ordinary
app for Scenes and session recovery. Hypertile does not manage connections
or host displays. Upgrade checks preserve unresolved host recovery and user
configuration.

- `SUPER+SHIFT+arrows` move a window into an empty slot of the layout as well
as swapping with a neighbour, so a lone app can travel around a sparse
layout. Explicit moves reveal collapsed slots until the layout is reset.

- Session recovery: batched automatic checkpoints with durable publication and
previous generations; named sessions; protected partial restores; supported
Expand Down
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ everything else in place, and is the one to run again after every update:
- the engine and bridge into `~/.config/hypr/`, and the two shipped layouts
into `~/.config/hypr/layouts/` (a layout that already exists is left alone)
- `hypertile-ctl` into `~/.local/bin/`
- the session recovery service, started by the layout loader
- the session recovery and independent Scenes services, started by the layout loader
- a `require("hypr.hypertile-layouts")` line in `hyprland.lua`
- the bar widget after the workspaces (skipped when it is already on the bar)
- a **Layouts** entry in the `SUPER+SPACE` menu (`--no-menu` skips it)
Expand All @@ -47,7 +47,11 @@ everything else in place, and is the one to run again after every update:
overlay; `SUPER+L` cycles the workspace through your layouts and then
dwindle, replacing Omarchy's dwindle/scrolling toggle, which cannot
return to a Lua layout; `SUPER+SHIFT+L` cycles the other way
- `SUPER+Arrow` focuses and `SUPER+SHIFT+Arrow` swaps with the nearest window in that direction,
- `SUPER+Arrow` focuses the nearest window in that direction. `SUPER+SHIFT+Arrow`
moves to the next layout slot: an empty slot receives the active window, and an
occupied slot swaps windows. Other apps stay in their slots; spacers and scene
slots marked Empty are skipped. Moving into a collapsed slot reveals the full
layout on that workspace until the layout is reset.

Every config file it edits is first copied to `<file>.hypertile.bak`. It
then reloads Hyprland and checks `hyprctl configerrors`. Update with:
Expand Down Expand Up @@ -302,16 +306,20 @@ Two layouts ship:
manifest.json the Omarchy plugin manifest (kinds: overlay, bar-widget)
plugin/ the shell plugin: Overlay.qml, Rail.qml (inspector), ZoneItem.qml,
Divider.qml, Thumb.qml, Card.qml, Chip.qml, Geometry.js (drawing),
Editor.js (edits); LayoutWidget.qml (bar widget)
Editor.js (edits); ContentPane.qml and Content.js (the Scenes tab);
LayoutWidget.qml (bar widget)
hypertile.lua engine: spec -> layout provider (hot-swappable)
hypertile-bridge.lua bridge: load/serialize/JSON/save/preview/apply
hypertile-json.lua JSON encode/decode (pure Lua)
hypertile-layouts.lua loader: requires every ~/.config/hypr/layouts/*.lua
hypertile-navigation.lua gap-aware focus and swap for SUPER+arrows and SUPER+SHIFT+arrows
hypertile-session.lua compositor adapter: capture and restore window placement
session/service.py session watcher, durable snapshots, app launch and matching
scenes/*.py scenes service: saved scenes, the app catalog, one-shot placement
layouts/*.lua shipped layouts: ultrawide, quad
bin/hypertile-ctl CLI over the bridge
bin/hypertile-session session service entry point (also via hypertile-ctl session)
bin/hypertile-scenes scenes service entry point (also via hypertile-ctl scene)
install.sh puts the engine, CLI, layouts, keybinds, and menu entry in place
uninstall.sh takes them out again
probe.lua live probe (logs everything the API hands a layout)
Expand Down Expand Up @@ -367,6 +375,21 @@ put them (within 1px on stacked heights, where hypertile rounds edges
instead of sizes to avoid seams). The CLI runs from a checkout without
installing: `HYPERTILE_SRC=$PWD bin/hypertile-ctl list`.

## Remote desktops

Scenes can launch or reuse installed apps in named zones, including each
computer's Remote Desktops launcher. Placement happens once; subsequent window
moves and closes stay under your control. The independent Scenes service does
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.
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

MIT. See [LICENSE](LICENSE).
15 changes: 15 additions & 0 deletions bin/hypertile-ctl
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +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)
scene <command> [...] list, save, apply, current, restore, retry, content;
see docs/SCENES.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 @@ -442,6 +444,19 @@ end

local argv = { ... }
local cmd = table.remove(argv, 1)
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 == "scene" then
if #argv == 0 then argv[1] = "current" end
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, " "))
os.exit(ok and 0 or (code or 1))
end
if not cmd or cmd == "-h" or cmd == "--help" then
cmd = "help"
end
Expand Down
12 changes: 12 additions & 0 deletions bin/hypertile-scenes
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env python3
"""Entry point for the installed scene service (or HYPERTILE_SRC)."""
import os
from pathlib import Path
import runpy
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 / "scenes"))
runpy.run_path(str(root / "scenes/scene_service.py"), run_name="__main__")
2 changes: 2 additions & 0 deletions bin/hypertile-session
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import os
from pathlib import Path
import runpy
import sys

source = os.environ.get("HYPERTILE_SRC")
root = Path(source) if source else Path(os.environ.get("XDG_DATA_HOME") or Path.home() / ".local/share") / "hypertile"
sys.path.insert(0, str(root / "session"))
runpy.run_path(str(root / "session/service.py"), run_name="__main__")
Loading
Loading