-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
KeepPressing is split into a pure domain core with zero Windows dependencies and a WinUI 3 app that wraps the OS. The core talks to the outside world through a single port — a lightweight hexagonal ("ports & adapters") arrangement.
KeepPressing (WinUI 3 app) KeepPressing.Core
┌───────────────────────────────────┐ ┌─────────────────────────────┐
│ MainPage ── MainPageViewModel │ │ PressEngine (state machine)│
│ (CommunityToolkit.Mvvm)│ │ PressSpec │
│ │ │ uses │ InputTarget (Mouse | Key) │
│ ┌────────────┼────────────┐ │ ─────────▶ │ PressMode (Repeat | Hold)│
│ ▼ ▼ ▼ │ │ EngineState (Idle | Running)│
│ Presentation Interop DI │ │ │
│ (SpecBuilder) (Win32) (Composition)│ │ IInputSynthesizer ◀── port│
└───────────────────┬────────────────┘ └───────────▲─────────────────┘
│ implements │
└─────────── Win32InputSynthesizer ────────┘
(SendInput adapter)
No reference to Win32 or WinUI. It owns the state machine (PressEngine), the domain types
(InputTarget, PressMode, EngineState, PressSpec, and primitives like ScreenPoint/KeyCode),
and one port: IInputSynthesizer. All side effects flow through that port, which makes the engine
fully unit-testable with a fake. Timing is injected via TimeProvider (real PeriodicTimer in
production, FakeTimeProvider in tests). See Domain Model & ADTs.
Every P/Invoke lives behind an interface in this folder: Win32InputSynthesizer (the SendInput
adapter for the port), HotkeyListener (global hotkeys), CursorLocator (GetCursorPos), ImeGuard,
WindowHandleProvider, plus the VirtualScreen multi-monitor coordinate helper. Native bindings are
generated by CsWin32 from Interop/NativeMethods.txt. See Win32 Interop.
Pure functions that map UI state to/from domain types: SpecBuilder turns the UI selection
(PressConfiguration) into a validated PressSpec, and SpecDescriber turns engine state into the
human-readable status text. Both take an ILocalizer, so they're testable without the WinUI resource
system.
MainPageViewModel (built on CommunityToolkit.Mvvm [ObservableProperty] / [RelayCommand]) holds UI
state, owns the commands, and marshals every event from Interop/Core (Pressed, StateChanged,
Faulted) back onto the UI thread via IUiDispatcher. Selection.cs defines the UI-only choice types
(TargetKind, PressModeKind, Choice<T>, HotkeyChoice).
App.OnLaunched builds the DI container via ServiceCollectionExtensions.AddKeepPressing. The input
synthesizer, hotkey listener, cursor locator, IME guard, dispatcher, localizer and the PressEngine
are all singletons (each owns a single piece of process state — a dedicated thread or the running
loop); the MainPageViewModel is transient. Because WinUI Pages are constructed parameterlessly,
MainPage resolves its ViewModel from App.Services.
On window close the app disposes the container asynchronously so that PressEngine.DisposeAsync
can wait for any held key/button to be released first. See
Threading & ConfigureAwait.
- Make illegal states unrepresentable — closed ADTs for targets/modes/state.
-
One side-effect boundary — everything OS-touching is behind
IInputSynthesizer(or an Interop interface). - Single source of truth for choices — option lists live in the ViewModel, bound by XAML, not duplicated in markup.
Deeper dives: Domain Model & ADTs · Threading & ConfigureAwait · Win32 Interop
KeepPressing · Releases · Issues · Licensed under Apache-2.0
Getting started
User guide
Developer guide