279 lines
11 KiB
Markdown
279 lines
11 KiB
Markdown
# Building and developing FancyWin
|
|
|
|
This document covers source builds, cross-compilation, verification, release
|
|
artifacts, dependencies, and the Windows implementation. End-user instructions
|
|
are in [README.md](README.md).
|
|
|
|
## Requirements
|
|
|
|
- Go 1.24 or later
|
|
- PowerShell for the supplied Windows build script, or a POSIX shell for manual
|
|
cross-compilation
|
|
- Windows 11 for runtime integration testing
|
|
|
|
The project uses pure Go and sets `CGO_ENABLED=0`. No C compiler, Windows SDK,
|
|
.NET SDK, Visual Studio, or PowerToys checkout is required.
|
|
|
|
## Repository layout
|
|
|
|
```text
|
|
cmd/fancywin/ CLI entry point
|
|
internal/config/ YAML model, loading, and validation
|
|
internal/layout/ Platform-neutral zone geometry
|
|
internal/platform/platform_windows.go Win32 backend
|
|
internal/platform/platform_other.go Non-Windows diagnostic stub
|
|
fancywin.example.yaml Example user configuration
|
|
build.ps1 Native Windows build script
|
|
dist/ Generated portable artifacts
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
Runtime functionality uses the Windows system DLLs available with Windows 11.
|
|
There are two pinned Go module dependencies:
|
|
|
|
- `golang.org/x/sys/windows` for Windows handles, callbacks, process queries,
|
|
and UTF-16 helpers
|
|
- `gopkg.in/yaml.v3` for strict YAML parsing
|
|
|
|
Both modules are statically linked into the executable. Exact versions and
|
|
checksums are recorded in `go.mod` and `go.sum`.
|
|
|
|
## Native Windows build
|
|
|
|
From PowerShell in the repository root:
|
|
|
|
```powershell
|
|
.\build.ps1
|
|
```
|
|
|
|
The script:
|
|
|
|
1. Selects `amd64` or `arm64` from `PROCESSOR_ARCHITECTURE`.
|
|
2. Sets `GOOS=windows` and disables CGO.
|
|
3. Runs the complete Go test suite.
|
|
4. Builds a stripped, reproducible-path executable.
|
|
5. Writes `dist\fancywin.exe` and copies the example to
|
|
`dist\fancywin.yaml`.
|
|
|
|
The generated executable is a console subsystem application so users can see
|
|
configuration and Win32 errors directly.
|
|
|
|
## Cross-compiling
|
|
|
|
Build Windows x64 from Linux, macOS, or another Go-supported host:
|
|
|
|
```sh
|
|
mkdir -p dist
|
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \
|
|
-buildvcs=false \
|
|
-trimpath \
|
|
-ldflags='-s -w' \
|
|
-o dist/fancywin.exe \
|
|
./cmd/fancywin
|
|
cp fancywin.example.yaml dist/fancywin.yaml
|
|
```
|
|
|
|
For Windows on ARM:
|
|
|
|
```sh
|
|
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build \
|
|
-buildvcs=false \
|
|
-trimpath \
|
|
-ldflags='-s -w' \
|
|
-o dist/fancywin-arm64.exe \
|
|
./cmd/fancywin
|
|
```
|
|
|
|
`-trimpath` removes local source paths. `-s -w` removes symbol and DWARF tables
|
|
to reduce the portable binary size. `-buildvcs=false` permits builds from source
|
|
archives or workspaces without complete Git metadata.
|
|
|
|
## Verification
|
|
|
|
Run platform-neutral tests and race detection on the development host:
|
|
|
|
```sh
|
|
go test -buildvcs=false ./...
|
|
go test -buildvcs=false -race ./...
|
|
```
|
|
|
|
Validate Windows-only code without executing it:
|
|
|
|
```sh
|
|
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go vet -buildvcs=false ./...
|
|
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -buildvcs=false ./cmd/fancywin
|
|
GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -buildvcs=false ./cmd/fancywin
|
|
```
|
|
|
|
Validate the example configuration on any supported development host:
|
|
|
|
```sh
|
|
go run -buildvcs=false ./cmd/fancywin \
|
|
-config fancywin.example.yaml \
|
|
-check
|
|
```
|
|
|
|
Cross-compilation proves that the Windows API bindings type-check; it does not
|
|
replace runtime testing on a Windows desktop.
|
|
|
|
## Windows smoke-test checklist
|
|
|
|
Test both x64 and ARM64 where hardware is available:
|
|
|
|
1. Start FancyWin without elevation and confirm the monitor names and work areas
|
|
are printed.
|
|
2. Confirm a second instance exits with a single-instance error.
|
|
3. Shift-drag Win32, UWP/Windows App SDK, and Chromium-based windows into each
|
|
zone.
|
|
4. Exercise all configured hotkey actions, including wrap-around.
|
|
5. Test monitors with negative virtual-screen coordinates, different DPI
|
|
scaling, portrait orientation, and taskbars on different edges.
|
|
6. Maximize a window and then move it by hotkey, checking visible-frame
|
|
alignment.
|
|
7. Edit valid YAML and confirm it reloads within roughly two seconds.
|
|
8. Introduce invalid YAML and confirm the previous configuration stays active.
|
|
9. Verify exact and substring exclusions.
|
|
10. Confirm a normal process cannot move an elevated application and that a
|
|
same-integrity elevated run can do so.
|
|
11. Disconnect and reconnect a monitor and confirm current work-area information
|
|
is used for subsequent moves.
|
|
|
|
## Architecture
|
|
|
|
### Process model
|
|
|
|
FancyWin is a single process and a single portable executable. The main
|
|
goroutine is locked to its OS thread because Windows delivers the out-of-context
|
|
accessibility callback and thread hotkey messages through that thread's message
|
|
queue. The program does not inject code into other processes or install a hook
|
|
DLL, service, shell extension, or driver.
|
|
|
|
At startup the backend:
|
|
|
|
1. Requests per-monitor-v2 DPI awareness.
|
|
2. Creates a named mutex for single-instance enforcement.
|
|
3. Installs a `SetWinEventHook` covering `EVENT_SYSTEM_MOVESIZESTART` through
|
|
`EVENT_SYSTEM_MOVESIZEEND` with `WINEVENT_OUTOFCONTEXT` and
|
|
`WINEVENT_SKIPOWNPROCESS`.
|
|
4. Registers configured system-wide hotkeys against the current thread.
|
|
5. Creates a two-second timer for configuration reload checks and a 25 ms timer
|
|
for sampling the mouse button, Shift, and active drag state.
|
|
6. Enters a standard Win32 `GetMessage` loop.
|
|
|
|
### Mouse snapping
|
|
|
|
The move-start callback records a manageable top-level window. On move-end, the
|
|
backend uses the most recently sampled asynchronous Shift-key state, obtains the
|
|
pointer's monitor, resolves the applicable layout, and finds the first zone
|
|
containing the pointer. Sampling throughout the drag avoids losing activation
|
|
when Windows delivers the move-end accessibility event just after a key-state
|
|
transition. The backend then converts that percentage zone into the monitor work
|
|
area's physical coordinates and calls `SetWindowPos`.
|
|
|
|
The configured gap affects the destination rectangle but not zone hit testing.
|
|
This avoids dead pointer strips between adjacent zones.
|
|
|
|
Some Windows environments do not deliver the move/size accessibility events
|
|
reliably. The 25 ms input timer provides an independent fallback: at the initial
|
|
left-button press it records the manageable top-level window beneath the
|
|
pointer, observes whether that window's rectangle actually changes while the
|
|
button remains down, tracks Shift, and snaps on release. Ordinary clicks are
|
|
discarded because the recorded rectangle did not move. Coordination state
|
|
prevents the accessibility and polling paths from snapping the same drag twice.
|
|
|
|
### Keyboard snapping
|
|
|
|
`RegisterHotKey` posts `WM_HOTKEY` to the message-loop thread. The backend obtains
|
|
the foreground window and its nearest monitor. Direct `zone_N` actions use a
|
|
one-based index. Previous/next actions find the zone whose center is closest to
|
|
the current window center and wrap through the monitor's ordered zone list.
|
|
|
|
`MOD_NOREPEAT` prevents a held shortcut from generating repeated moves.
|
|
|
|
### Coordinates and visible frames
|
|
|
|
Zone percentages are resolved against `MONITORINFOEX.rcWork`, not the complete
|
|
monitor rectangle, so taskbar and app-bar space is excluded. Per-monitor-v2 DPI
|
|
awareness keeps pointer, monitor, and window coordinates in one physical-pixel
|
|
coordinate system across mixed-DPI displays.
|
|
|
|
Windows 11 commonly includes an invisible resize border in `GetWindowRect`.
|
|
Before moving a window, FancyWin compares that rectangle with
|
|
`DWMWA_EXTENDED_FRAME_BOUNDS` and expands the outer `SetWindowPos` rectangle so
|
|
the visible frame aligns with the requested zone.
|
|
|
|
### Overlay rendering
|
|
|
|
The overlay is implemented directly with User32 and GDI. While an active mouse
|
|
drag and Shift are both detected, FancyWin creates one borderless popup window
|
|
covering the usable work area of the monitor beneath the pointer. The window
|
|
uses `WS_EX_LAYERED`, `WS_EX_TRANSPARENT`, `WS_EX_TOOLWINDOW`,
|
|
`WS_EX_NOACTIVATE`, and `WS_EX_TOPMOST` so it remains visible without receiving
|
|
input, taking focus, or appearing in Alt+Tab.
|
|
|
|
The background is painted with a color key made transparent by
|
|
`SetLayeredWindowAttributes`. GDI then draws the gap-adjusted zone rectangles
|
|
and centered Segoe UI labels in the configured color and opacity. The overlay is
|
|
destroyed when Shift or the left mouse button is released. Moving the pointer to
|
|
another monitor recreates it with that monitor's work area and selected layout.
|
|
|
|
### Window filtering and permissions
|
|
|
|
The backend ignores invisible windows, child windows, owned windows, and
|
|
configured executable exclusions. Process names are obtained with
|
|
`PROCESS_QUERY_LIMITED_INFORMATION` and `QueryFullProcessImageName`.
|
|
|
|
Windows User Interface Privilege Isolation prevents a lower-integrity process
|
|
from reliably controlling a higher-integrity window. FancyWin does not attempt
|
|
to bypass that boundary or request elevation through a manifest.
|
|
|
|
### Configuration reload
|
|
|
|
The YAML decoder rejects unknown fields. Semantic validation checks the schema
|
|
version, gap range, active-layout existence, unique layout-name/monitor pairs,
|
|
zone bounds, hotkey action syntax, and required values. Legacy files in which
|
|
all layouts are unnamed and `active_layout` is absent are normalized to a
|
|
single layout name of `default` before validation.
|
|
|
|
The message-loop timer checks the configuration modification time. A valid new
|
|
configuration replaces the active one and re-registers hotkeys. If parsing,
|
|
validation, or hotkey registration fails, the previous valid configuration and
|
|
hotkeys are restored.
|
|
|
|
## Current scope
|
|
|
|
The current backend intentionally excludes:
|
|
|
|
- A visual layout editor
|
|
- Low-level keyboard interception of Windows-reserved snap shortcuts
|
|
- Multi-zone selection and expansion
|
|
- Moving windows across monitors as part of next/previous traversal
|
|
- Persistence of application-to-zone history
|
|
- Automatic placement of newly created windows
|
|
- Virtual-desktop-specific layouts
|
|
- Layout switching shortcuts
|
|
- Rounded-corner control
|
|
|
|
These should be treated as separate features because several require additional
|
|
state, UI surfaces, or lower-level input handling.
|
|
|
|
## Release preparation
|
|
|
|
For a distributable release:
|
|
|
|
1. Run all tests, vet, both architecture builds, and the Windows smoke-test
|
|
checklist.
|
|
2. Update the `version` constant in `cmd/fancywin/main.go`.
|
|
3. Build x64 and ARM64 artifacts with `CGO_ENABLED=0`, `-trimpath`, and
|
|
`-ldflags='-s -w'`.
|
|
4. Include an example renamed to `fancywin.yaml` beside each executable.
|
|
5. Record SHA-256 hashes with `Get-FileHash` or `sha256sum`.
|
|
6. Code-sign public binaries if a trusted signing certificate and release
|
|
process are available. Signing is not required for portability, but unsigned
|
|
downloads may receive stronger Microsoft Defender SmartScreen warnings.
|
|
|
|
PowerToys was used as behavioral and architectural reference material only.
|
|
FancyWin is an independent implementation and contains no copied PowerToys
|
|
source code.
|