Skip to content
Draft
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: 26 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ on:
workflow_dispatch:

permissions:
contents: write
contents: read

jobs:
build-windows:
runs-on: windows-latest

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Setup Node
uses: actions/setup-node@v6
Expand All @@ -41,6 +41,15 @@ jobs:
throw "Tag ${{ github.ref_name }} does not match package version $expected"
}

- name: Validate distribution license
if: startsWith(github.ref, 'refs/tags/')
shell: pwsh
run: |
$license = Get-Content LICENSE -Raw
if ($license -match "not been finalized|Do not redistribute") {
throw "Public releases are blocked until LICENSE contains final distribution terms"
}

- name: TypeScript check
run: npm run check

Expand Down Expand Up @@ -86,14 +95,27 @@ jobs:
src-tauri/target/release/bundle/**/*.msi
src-tauri/target/release/bundle/**/*.sha256

publish-release:
if: startsWith(github.ref, 'refs/tags/')
needs: build-windows
runs-on: windows-latest
permissions:
contents: write

steps:
- name: Download Windows bundle
uses: actions/download-artifact@v8
with:
name: WindowAutoLayout-Windows
path: release-artifacts

- name: Publish tagged release
if: startsWith(github.ref, 'refs/tags/')
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
$tag = "${{ github.ref_name }}"
$files = Get-ChildItem "src-tauri/target/release/bundle" -Recurse -File |
$files = Get-ChildItem "release-artifacts" -Recurse -File |
Where-Object { $_.Extension -in ".exe", ".msi", ".sha256" } |
ForEach-Object { $_.FullName }

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
.tools/
src-tauri/target
src-tauri/gen
*.pdb
Expand Down
81 changes: 81 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,86 @@
# Changelog

## 0.1.28

- Added a live tray command named `Restore windows now`, a checked automatic-restore status item, and an optional restore-on-left-click tray action.
- Added persistent restore and automatic-state controls to every app view, with tray and window state kept in sync while a restore is running.
- Added separate settings for Show Desktop recovery, post-game recovery, launching closed apps, and tray left-click behavior.
- Saved display resolution, work area, and scale metadata with each captured window so layouts adapt cleanly to resolution or taskbar changes while staying exact on unchanged displays.
- Replaced volatile `DISPLAY1` and `DISPLAY2` targets with hardware-backed monitor identities, including a guarded migration for layouts saved before Windows reordered display names.
- Added bounded placement verification with repeated geometry checks, one-pixel tolerance, and clear expected-versus-actual failures when another app refuses its saved bounds.
- Waited past undersized launch and update splash windows before placing the real app surface, preventing late startup windows from reclaiming old bounds.
- Kept already-running minimized or tray-hidden apps recoverable when launching closed apps is disabled, and made startup restore honor that preference.
- Made restore and capture results independent of optional log-file writes, tightened imported display metadata, and made layout-lock state changes transactional.
- Kept the WebView unloaded in tray-only mode and destroyed it when the interface closes, leaving one low-priority native process with no idle polling.
- Updated the interface, newest-first activity log, documentation screenshot, frontend dependencies, and release checks; frontend and Rust advisory scans report no known vulnerabilities.

## 0.1.26

- Limited OBS tray activation to truly hidden windows so a minimized OBS window is never toggled back into the tray.
- Added an ordered OBS repaint with a one-shot client-surface check and one non-minimizing retry when the title bar appears but the Qt interface is still blank.
- Prevented restore-generated window events from feeding back into the event-driven layout lock and stopped generic window restores from minimizing windows as a side effect.

## 0.1.25

- Made background restores show windows asynchronously without activating them or waiting on another app's UI thread.
- Added a shared renderer refresh for restored windows, including windows that already match their saved rectangle after leaving the tray.
- Replaced synchronous forced paints with a one-shot resize and queued full-surface repaint, preventing blank client areas without adding idle work.
- Routed minimized OBS windows through OBS's native Qt tray restore handler before using the generic Win32 fallback.
- Added a bounded, state-verified OBS minimize/restore recovery so Qt rebuilds and presents its docks instead of leaving a white client area.
- Blocked OBS presentation recovery while a game or fullscreen app is foreground and restored the previous window only when focus stayed on OBS during the pulse.

## 0.1.24

- Made OpenLaunchDeck restores use its supported `--show` single-instance command, so a startup tray process exposes the existing window without creating a duplicate.
- Removed OpenLaunchDeck background-only arguments during a layout restore while preserving unrelated custom launch arguments.
- Added a no-focus Qt relayout and full child repaint after OBS is moved, returning it to the exact saved rectangle before the restore completes.

## 0.1.23

- Serialized restore requests so startup, tray, dashboard, and event-lock restores cannot move the same windows at the same time.
- Reported restores paused by a foreground game as paused instead of success, and kept the event guard ready to retry after the game exits.
- Preserved maximized state when capturing layouts and constrained oversized saved bounds after monitor resolution changes.
- Made nearest-monitor fallback compare the saved profile canvas against every connected display.
- Added safe config import parsing, schema normalization, duplicate-ID repair, regex validation, and bounds for imported delays and rectangles.
- Made a second launch bring the existing tray app forward and made the close-button setting exit cleanly when tray hiding is disabled.
- Removed settings that did not affect runtime behavior and made fullscreen game protection unconditional.
- Isolated page scrolling inside the app shell, reset scroll position between views, and added an interface recovery screen.

## 0.1.22

- Removed the global hotkey and disabled Tauri raw mouse and keyboard device events, so WindowAutoLayout no longer registers for background input.
- Replaced the five-second layout-lock poller with a blocking Windows event guard for Show Desktop and game-to-desktop transitions.
- Prevented automatic and startup restores from forcing window focus or synthesizing keyboard input.
- Added mid-restore game checks so background work stops if a game or fullscreen app becomes active.
- Coalesced restore events through one low-priority worker instead of creating a thread for every shell event.
- Tightened the Tauri content security policy and removed the unused opener plugin.
- Kept Microsoft Store app profiles working across versioned WindowsApps folder and executable-name changes.
- Redesigned the dashboard, layout editor, app editor, settings, profiles, and logs around a denser desktop control surface.

## 0.1.21

- Added an event-driven desktop guard that restores already-running profile windows after Show Desktop or after leaving a latency-sensitive game.
- Kept the guard idle while games such as Valorant are foreground and restored without launching missing apps.
- Left the installed app running after local installs instead of closing the smoke-launch process.
- Prevented duplicate tray processes with a named single-instance guard.
- Accepted BOM-marked config files so a valid config is not replaced with defaults.
- Kept startup registration pointed at `WindowAutoLayout.exe --startup-restore` so the tray app comes back on Windows login.

## 0.1.20

- Started the app hidden during startup restore so no WindowAutoLayout window is created on-screen before tray mode.
- Lowered WindowAutoLayout process priority and the layout-lock worker thread priority so the game scheduler wins.
- Raised layout-lock polling to a 5-30 second range and paused it for 60 seconds while Valorant or another latency-sensitive game is foreground.
- Treated tray-only OpenLaunchDeck as a clean no-op instead of relaunching it or marking the restore partial.
- Kept the exact-placement and low-CPU restore fixes from 0.1.19.

## 0.1.19

- Restored normal windows after unmaximizing before the final move, so Windows does not undo the saved bounds.
- Rechecked restored windows and retried the saved rectangle once if Windows reported drift after the move.
- Kept stale or off-screen saved layouts on the target monitor while preserving exact saved bounds when they still touch the monitor.
- Made Capture Current Layout use the window's real monitor, falling back to its center point only when Windows does not report a monitor.

## 0.1.18

- Cut layout lock CPU use by only running full restore work when managed windows actually change.
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ powershell -ExecutionPolicy Bypass -File scripts\install-current.ps1 -SkipBuild

- Keep Win32 calls inside the Rust backend.
- Avoid permanent busy loops.
- Clamp polling intervals for lock/restore behavior.
- Prefer blocking Windows events over recurring polling for background behavior.
- Do not add global mouse hooks, keyboard hooks, raw-input listeners, or synthetic input.
- Treat hidden tray windows, minimized windows, and delayed windows as normal cases.
- Return clear errors to the UI instead of panicking.
- Keep matching conservative enough to avoid tool windows and helper windows.
Expand Down
57 changes: 43 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,23 @@

Streaming and gaming setups usually need the same windows in the same places: OBS on one side, Discord on another, a browser where chat or dashboards live, and everything ready after a reboot. Windows can remember some things, but tray apps, delayed startup windows, monitor offsets, and Show Desktop can still scramble a setup.

WindowAutoLayout saves workspace profiles and restores them from the app, tray menu, startup, or a hotkey. It is built for the everyday Windows workflow where pressing one button should bring the setup back, whether the apps are already open, minimized, hidden in the tray, or fully closed.
WindowAutoLayout saves workspace profiles and restores them from the app, tray menu, or Windows startup. It is built for the everyday Windows workflow where pressing one button should bring the setup back, whether the apps are already open, minimized, hidden in the tray, or fully closed.

## What It Does

- Saves app layouts per monitor using monitor-relative coordinates.
- Saves app layouts against a hardware-backed monitor identity using monitor-relative physical-pixel coordinates plus captured display and work-area metadata.
- Captures the visible windows on a selected monitor into the active profile.
- Detects top-level windows with title, class name, process name, PID, bounds, visibility, minimized state, and executable path when Windows exposes it.
- Restores a profile by launching missing apps, waiting for real matching windows, then moving and resizing them with Win32 APIs.
- Restores a profile by launching missing apps, waiting past splash windows for the real matching surface, then moving and resizing it with Win32 APIs.
- Pulls minimized and hidden tray windows forward before treating an app as missing.
- Handles OBS in the tray by asking OBS through its tray icon path, then waiting for the real OBS window to repaint before applying the saved layout.
- Keeps a selected profile locked while the lock is on, so Show Desktop, accidental minimize, and accidental moves get snapped back.
- Supports editable app presets, multiple profiles, startup restore, tray restore, logs, JSON import/export, and a global hotkey.
- Handles OBS in the tray through its Qt tray path, applies the saved layout, then runs a bounded presentation recovery so the real interface is painted before restore finishes.
- Uses OpenLaunchDeck's `--show` single-instance handoff to restore its existing startup tray process instead of launching a duplicate.
- Refreshes restored window surfaces without activating them, so apps do not remain blank until clicked.
- Keeps a selected profile protected with an event-driven lock that reacts to Show Desktop and game-to-desktop transitions without a polling loop.
- Reopens the existing tray instance when WindowAutoLayout is launched a second time instead of creating a duplicate process.
- Does not install global mouse hooks, keyboard hooks, raw-input listeners, or system-wide hotkeys.
- Keeps a live `Restore windows now` command and checked automatic-restore state in the tray menu.
- Supports editable app presets, multiple profiles, startup restore, configurable tray left-click behavior, logs, and JSON import/export.
- Stores config and logs locally. No telemetry, accounts, analytics, or background network calls.

## Download
Expand All @@ -58,24 +63,47 @@ After install, WindowAutoLayout lives under the current Windows user profile and
4. Open and arrange the apps you want in the profile.
5. On Dashboard, pick the capture monitor and press Capture current layout.
6. Fine-tune app matching on Apps or individual windows on Layout if needed.
7. Press Restore from Dashboard, the tray menu, startup restore, or `Ctrl+Alt+L`.
7. Press Restore windows now from any app view or the tray menu.
8. Turn on Automatic restore if Show Desktop or returning from a game should recover that profile.

For a complete setup walkthrough, see [docs/usage.md](docs/usage.md).

## Tray Controls

Right-click the tray icon for the current runtime controls:

- `Restore windows now` runs the selected startup/default profile immediately.
- `Automatic restore: On/Off` shows the real state and toggles recovery for the selected automatic profile.
- `Open WindowAutoLayout` opens the existing single app instance.
- `Open activity log` opens the local restore log.
- `Exit` stops the tray process.

Settings can make a normal left-click either open WindowAutoLayout or restore the layout immediately. While a restore is active, the tray label, tooltip, and app header show that state and block duplicate restore requests.

## OBS And Tray Apps

OBS can stay running in the system tray with Replay Buffer on. For the OBS app entry, keep these enabled:

- `Pull hidden/tray windows`
- `Wake running tray apps`

When OBS is fully hidden in the tray, Windows may report no normal main window. WindowAutoLayout handles that by sending OBS the same tray activation path a manual tray click uses, waiting for OBS to show and repaint, then moving it into the saved layout. That avoids starting a duplicate OBS process and avoids moving the window while OBS is still a blank shell.
When OBS is fully hidden in the tray, Windows may report no normal main window. WindowAutoLayout handles that by sending OBS the same Qt tray activation path a manual tray click uses, waiting for the main window, applying the saved rectangle, and running a bounded state-verified presentation recovery. That avoids starting a duplicate OBS process and prevents the main frame from being left as a blank shell.

Background restores never fake a click or leave each restored app focused. The generic path uses Windows' non-activating show behavior, applies the saved rectangle, and queues a one-shot surface repaint. OBS uses the bounded recovery above and returns to the previous window when focus did not change. No repaint timer runs after the restore completes.

More details and recovery checks are in [docs/troubleshooting.md](docs/troubleshooting.md).

## Layout Lock
## Automatic Restore

The automatic control arms a Windows shell-event guard for the selected profile. Show Desktop recovery and post-game recovery can be toggled separately. The guard does not run a timer or repeatedly inspect every window. Background restores never launch missing apps, never force focus, and stop immediately if a game or fullscreen app becomes active again.

Manual and startup restores can launch closed apps when `Launch apps that are closed` is enabled. Turning that setting off still allows an already-running minimized or tray-hidden app to be recovered.

## Game And Input Safety

WindowAutoLayout does not register global hotkeys or low-level mouse/keyboard hooks. Tauri's optional raw device-event stream is explicitly filtered out, the process and restore worker run below normal priority, and the background guard blocks on Windows accessibility events instead of polling. Window movement uses normal Win32 window-management calls and does not synthesize keyboard or mouse input.

The lock button watches the selected profile and only runs a full restore when one of its managed windows changes position, visibility, minimized state, or disappears. It is meant for live setups where the layout should recover after Show Desktop, accidental minimize, dragging, or another app stealing placement without burning CPU while everything is already stable. The lock interval is clamped to a low-impact range, and fullscreen non-profile apps get a longer pause so games are not polled hard.
The WebView interface is created only while the app window is open. Closing to tray destroys the WebView process tree and leaves the small native tray process running, so normal tray use does not carry a hidden browser runtime through a game.

## Startup Restore

Expand All @@ -85,7 +113,7 @@ Startup restore uses:
"<installed WindowAutoLayout.exe>" --startup-restore
```

The app can start minimized to tray, wait for configured startup delay seconds, restore the default startup profile, launch missing apps, then keep the layout locked if that option is enabled.
The app can start minimized to tray, wait for the configured startup delay, restore the default profile, optionally launch closed apps, and keep automatic restore armed if it is enabled.

## Matching Rules

Expand All @@ -103,6 +131,7 @@ Useful notes:

- OBS may take longer while plugins and docks load.
- Discord uses multiple Electron processes and may appear late.
- Startup and updater splash windows are ignored until a launch-ready surface appears.
- Steam can show update or login windows before the main window.
- Browser titles change with tabs.
- Elevated apps may block moves from a normal, non-elevated WindowAutoLayout process.
Expand Down Expand Up @@ -131,7 +160,7 @@ npm run check
cd src-tauri
cargo fmt --check
cargo check
cargo test
cargo test --all-targets
cargo clippy --all-targets -- -D warnings
cd ..
npm audit --audit-level=moderate
Expand Down Expand Up @@ -168,9 +197,9 @@ scripts/ Local install/update helper
Every version tag matching `v*` builds the Windows bundles and publishes installer assets to GitHub Releases.

```powershell
git tag v0.1.18
git tag v0.1.28
git push origin main
git push origin v0.1.18
git push origin v0.1.28
```

The workflow validates that the tag matches `package.json`, runs TypeScript, frontend audit, and Rust checks, builds the Tauri bundle, generates checksums, uploads CI artifacts, and publishes the release.
Expand Down
Binary file modified docs/assets/windowautolayout-apps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading