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
9 changes: 2 additions & 7 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
name: dev-build

on:
push:
pull_request:
branches:
- main
- dev
paths:
- "src/**"
workflow_dispatch:

jobs:
build:
Expand All @@ -22,10 +20,7 @@ jobs:
id: src_changes
shell: pwsh
run: |
$base = "${{ github.event.before }}"
if (-not $base -or $base -eq "0000000000000000000000000000000000000000") {
$base = "HEAD~1"
}
$base = "${{ github.event.pull_request.base.sha }}"
$changed = git diff --name-only $base $env:GITHUB_SHA | Where-Object { $_ -like "src/*" }
$hasChanges = $changed.Count -gt 0
"has_src_changes=$hasChanges" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
Expand Down
40 changes: 25 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A window manager written in AutoHotkey v2.
The aim is a low-friction workflow: a single super modifier, mnemonic app keys, and fast window actions. Alt+Tab and Win+Tab still work, but you will hardly use them

## Contents
- [What This Does](#what-this-does)
- [Overview](#overview)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Default Config Keys](#default-config-keys)
Expand All @@ -17,19 +17,18 @@ The aim is a low-friction workflow: a single super modifier, mnemonic app keys,
- [Layout](#layout)
- [Third-Party](#third-party)

## What This Does
## Overview

> [!NOTE]
> CapsLock is the default super key.
> CapsLock is the default super key, because who needs it?

### Features

Show the [Command Overlay](#command-overlay) when the super key is held.
![Alt text](docs/assets/command_overlay.png)

Jump focus to an app or launch it with `super + [letter]`.
Launch-or-focus a program with `super + [letter]`, or directionally change window focus with `alt + h/l/j/k` (left, right, down, up) and `alt + [` / `alt + ]` for back/forward in a stack.
![Alt text](docs/assets/focus.gif)

Cycle centered window widths with `super + space`.
Cycle centered window widths with `super + spacebar`.
![Alt text](docs/assets/center-cycle.gif)

Maximizes/restores with `super + m`.
Expand All @@ -44,12 +43,22 @@ Freely move a window with double tap super + h/j/k/l
Resize edges with `super + shift + h/j/k/l`.
![Alt text](docs/assets/resize.gif)

Show the [Command Overlay](#command-overlay) when the super key is held. Disable through command mode.
![Alt text](docs/assets/command_overlay.png)

Use the "window switcher" (like powertoys window walker) with `super + w`.
![Alt text](docs/assets/window_switcher.png)

Other
- `super + alt` send `ctrl + tab` (configurable via `global_hotkeys`)
- `super + c` to cycle through windows of the same app
- `super + alt` sends `ctrl + tab` (configurable via `global_hotkeys`)
- `super + c` cycle through windows of the same app
- `super + w` open Window Selector (fuzzy find open windows)
- `alt + h/l` move window focus left/right
- `alt + j/k` move window focus down/up (non-stacked)
- `alt + [` / `alt + ]` move window focus forward/back through stacked windows

Enter Command Mode with `super + ;`.
- `r` to reload app/config
- `r` to reload program/config
- `e` to open config file
- `w` opens a new window for the active program, if the program supports it
- `n` toggles the command overlay on or off
Expand All @@ -60,7 +69,7 @@ Enter Command Mode with `super + ;`.

### Quick start

- Start the program and enter command mode with `super + ;`.
- Start the program and enter command mode with `super + ;`. The binary is not currently signed and you will be warned by Windows. Clone and use `main.ahk` directly as an alternative.
- Press `e` to open the config file. You can also find it manually in `~/.config/be-there/config.json`.
- After making changes to your config you can reload the config (the entire program, actually) with `r` while in command mode.

Expand All @@ -70,7 +79,9 @@ Enter Command Mode with `super + ;`.
- `apps[].run_paths`: optional list of directories to search for the executable.
- `global_hotkeys`: array of scoped hotkey bindings (set `target_exes` empty for global use).
- `window`: resize/move steps and hotkeys (including move mode).
- `window_manager`: grid size, margins, and ignored window classes.
- `window_selector`: Window Selector settings (hotkey, match fields, display limits).
- `window_manager`: grid size, margins, gaps, and ignored window classes.
- `directional_focus`: directional focus settings (stacked threshold, stack tolerance, topmost preference, last-stacked preference, frontmost guard, perpendicular overlap min, cross-monitor, debug).
- `focus_border`: overlay appearance and update interval.
- `helper`: command overlay settings.
- `reload`: hotkey and file watch settings for config reload.
Expand All @@ -93,9 +104,8 @@ Enter Command Mode with `super + ;`.
- Use Refresh to update the list; Copy Selected/All or Export to save results.

## Known Limitations
- This has not been tested with multi-monitor setups.
- Dynamic grid ratios need to be added to support more screen sizes and resolutions for the window-snap function.
- Some apps (e.g., Discord) launch via `Update.exe` and keep versioned subfolders, which makes auto-resolution unreliable.
- This has not been tested with multi-monitor setups or much outside of ultra-wide monitors.
- Some apps (e.g., Discord) launch via `Update.exe` and keep versioned subfolders, which makes auto-resolution unreliable for launching or focusing more challenging.
- For some apps that minimize or close to the system tray, it's recommended you disable that in the program. Otherwise you can try to set `apps[].run` to a stable full path (or use `run_paths`) in your config.
- Windows with elevated permissions may ignore be-there hotkeys unless be-there is run as Administrator.

Expand Down
37 changes: 33 additions & 4 deletions be-there.ahk
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#Include src/lib/JXON.ahk
#Include src/lib/config_loader.ahk
#Include src/lib/state_store.ahk
#Include src/lib/focus_or_run.ahk
#Include src/lib/command_toast.ahk
#Include src/lib/window_inspector.ahk

Expand All @@ -17,6 +19,7 @@ if (config_errors.Length) {
LogConfigErrors(config_errors, config_dir "\config.errors.log", config_path)
ExitApp
}
global AppState := LoadState()
InitCommandToast()

super_key := Config["super_key"]
Expand Down Expand Up @@ -63,11 +66,14 @@ SetWinDelay(-1)
window_nav_modifier := super_key

#Include src/lib/window_manager.ahk
#Include src/lib/focus_or_run.ahk
#Include src/lib/directional_focus.ahk
#Include src/lib/focus_border.ahk
#Include src/lib/window_walker.ahk
#Include src/hotkeys/global_hotkey.ahk
#Include src/hotkeys/apps.ahk
#Include src/hotkeys/window.ahk
#Include src/hotkeys/directional_focus.ahk
#Include src/hotkeys/window_walker.ahk
#Include src/hotkeys/unbound.ahk

DefaultConfig() {
Expand Down Expand Up @@ -99,25 +105,48 @@ DefaultConfig() {
"cycle_app_windows_hotkey", "c",
"center_width_cycle_hotkey", "Space"
),
"window_selector", Map(
"enabled", true,
"hotkey", "w",
"max_results", 12,
"title_preview_len", 60,
"match_title", true,
"match_exe", true,
"include_minimized", true,
"close_on_focus_loss", true
),
"window_manager", Map(
"grid_size", 3,
"margins", Map(
"top", 6,
"left", 4,
"right", 4
),
"gap_px", 0,
"exceptions_regex", "(Shell_TrayWnd|Shell_SecondaryTrayWnd|WorkerW|XamlExplorerHostIslandWindow)"
),
"directional_focus", Map(
"enabled", true,
"stacked_overlap_threshold", 0.5,
"stack_tolerance_px", 25,
"prefer_topmost", true,
"prefer_last_stacked", true,
"frontmost_guard_px", 200,
"perpendicular_overlap_min", 0.2,
"cross_monitor", false,
"debug_enabled", false
),
"focus_border", Map(
"enabled", true,
"border_color", "0x357EC7",
"move_mode_color", "0x2ECC71",
"border_color", "#357EC7",
"move_mode_color", "#2ECC71",
"border_thickness", 4,
"corner_radius", 12,
"update_interval_ms", 20
),
"helper", Map(
"enabled", true
"enabled", true,
"overlay_opacity", 200
),
"reload", Map(
"enabled", true,
Expand Down
29 changes: 26 additions & 3 deletions config/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,48 @@
"cycle_app_windows_hotkey": "c",
"center_width_cycle_hotkey": "Space"
},
"window_selector": {
"enabled": true,
"hotkey": "w",
"max_results": 12,
"title_preview_len": 60,
"match_title": true,
"match_exe": true,
"include_minimized": true,
"close_on_focus_loss": true
},
"window_manager": {
"grid_size": 3,
"margins": {
"top": 6,
"left": 4,
"right": 4
},
"gap_px": 0,
"exceptions_regex": "(Shell_TrayWnd|Shell_SecondaryTrayWnd|WorkerW|XamlExplorerHostIslandWindow)"
},
"directional_focus": {
"enabled": true,
"stacked_overlap_threshold": 0.5,
"stack_tolerance_px": 25,
"prefer_topmost": true,
"prefer_last_stacked": true,
"frontmost_guard_px": 200,
"perpendicular_overlap_min": 0.2,
"cross_monitor": false,
"debug_enabled": false
},
"focus_border": {
"enabled": true,
"border_color": "0x357EC7",
"move_mode_color": "0x2ECC71",
"border_color": "#357EC7",
"move_mode_color": "#2ECC71",
"border_thickness": 4,
"corner_radius": 12,
"update_interval_ms": 20
},
"helper": {
"enabled": true
"enabled": true,
"overlay_opacity": 200
},
"reload": {
"enabled": true,
Expand Down
Binary file modified docs/assets/command_overlay.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/focus.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/window_switcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Cross platform shebang:
shebang := if os() == 'windows' {
'pwsh.exe'
} else {
'/usr/bin/env pwsh'
}

# Set shell for non-Windows OSs:
set shell := ["pwsh", "-c"]

# Set shell for Windows OSs:
set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"]


@build:
./tools/build_release.ps1

@start:
./be-there.ahk

@start_bin:
./dist/be-there.exe

compress_gifs path:
#!{{shebang}}
Get-ChildItem {{path}} -Filter *.gif | ForEach-Object {
$in = $_.FullName
$tmp = [System.IO.Path]::ChangeExtension($in, ".optimized.gif")
ffmpeg -y -i "$in" `
-vf "fps=11,scale=720:-2:flags=lanczos,split[s0][s1];[s0]palettegen=stats_mode=full[p];[s1][p]paletteuse=dither=bayer:bayer_scale=3" `
"$tmp"
}
12 changes: 12 additions & 0 deletions src/hotkeys/directional_focus.ahk
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
global Config

if Config.Has("directional_focus") && Config["directional_focus"]["enabled"] {
Hotkey("!h", (*) => DirectionalFocus("left"))
Hotkey("!l", (*) => DirectionalFocus("right"))
Hotkey("!j", (*) => DirectionalFocus("down"))
Hotkey("!k", (*) => DirectionalFocus("up"))
Hotkey("![", (*) => DirectionalFocusStacked("prev"))
Hotkey("!]", (*) => DirectionalFocusStacked("next"))
Hotkey("!+d", (*) => ToggleDirectionalFocusDebug())
Hotkey("!+s", (*) => SetLastStackedFromActive())
}
57 changes: 49 additions & 8 deletions src/hotkeys/window.ahk
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,25 @@ CenterWidthCycle(*) {
mw := mx2 - mx1
mh := my2 - my1

left_margin := Screen.left_margin
right_margin := Screen.right_margin
top_margin := Screen.top_margin
gap_px := Config["window_manager"]["gap_px"]

mx1 += left_margin
mw := mw - left_margin - right_margin
mh := mh - top_margin

if (gap_px > 0) {
mx1 += gap_px
my1 += gap_px
mw -= gap_px * 2
mh -= gap_px * 2
}

if (mw <= 0 || mh <= 0)
return

if (state = 0) {
; center 1/3
w := mw / 3
Expand All @@ -207,14 +226,7 @@ CenterWidthCycle(*) {
w := mw * 2 / 3
}

left_margin := Screen.left_margin
right_margin := Screen.right_margin
top_margin := Screen.top_margin

mx1 += left_margin
mw := mw - left_margin - right_margin
mh := mh - top_margin

w := Min(w, mw)
x := mx1 + (mw - w) / 2
y := my1 + top_margin

Expand Down Expand Up @@ -248,6 +260,14 @@ CloseWindow(*) {
WinClose "ahk_id " hwnd
}

MinimizeWindow(*) {
hwnd := WinExist("A")
if !hwnd
return
WinMinimize "ahk_id " hwnd
ActivateMostRecentWindow(hwnd)
}

CycleAppWindows(*) {
hwnd := WinExist("A")
if !hwnd
Expand Down Expand Up @@ -295,6 +315,8 @@ Hotkey("q", CloseWindow)
Hotkey(cycle_app_windows_hotkey, CycleAppWindows)
HotIf

Hotkey("!-", MinimizeWindow)

if move_mode_enabled {
HotIf Window.IsMoveMode
Hotkey("h", (*) => MoveActiveWindow(-move_step, 0))
Expand All @@ -309,3 +331,22 @@ ExitMoveMode() {
Window.SetMoveMode(false)
UpdateCommandToastVisibility()
}

ActivateMostRecentWindow(exclude_hwnd := 0) {
z_list := WinGetList()
for _, hwnd in z_list {
if (hwnd = exclude_hwnd)
continue
if Window.IsException("ahk_id " hwnd)
continue
if (WinGetMinMax("ahk_id " hwnd) = -1)
continue
ex_style := WinGetExStyle("ahk_id " hwnd)
if (ex_style & 0x80) || (ex_style & 0x8000000)
continue
if !(WinGetStyle("ahk_id " hwnd) & 0x10000000)
continue
WinActivate "ahk_id " hwnd
return
}
}
6 changes: 6 additions & 0 deletions src/hotkeys/window_walker.ahk
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
global Config, super_key

if Config.Has("window_selector") && Config["window_selector"]["enabled"] {
hotkey_name := Config["window_selector"]["hotkey"]
Hotkey(super_key " & " hotkey_name, (*) => WindowWalker.Show())
}
Loading