Release 1.1.0: fix display rule, shortcut clashes, stale menu and smart activation
- Detect an external display as any online display that isn't built in (CGGetOnlineDisplayList + CGDisplayIsBuiltin) instead of counting NSScreens, so the rule works in clamshell mode, when mirroring and on single-monitor desktops. The Smart Activation footer reports the same check. - Refuse and mark shortcut presets that clash with a macOS system shortcut (CopySymbolicHotKeys, checked once, only when the Global Shortcut submenu opens), report the real reason a registration failed, and stop claiming that other apps' clashes are detected. Show the shortcut hint on the toggle only when it is actually registered. - Make the menu toggle carry the action it displays, refresh the status line, toggle and "Add 30 minutes" while the menu is open, and ignore a click that lands as the toggle changes meaning, so a click never does the opposite of its label. - Replace the single suppression flag with per-rule overrides: the status names what is paused, Turn On becomes Resume Smart Activation, each rule re-arms when it stops holding, and a rule that became true during a timed session takes over at expiry while rules that held throughout stay paused. - Update the README to match, bump the version to 1.1.0 and add CHANGELOG.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2363fc2165
commit
ee2e22b2f7
@@ -72,8 +72,13 @@ Two design decisions worth calling out:
|
||||
**The menu is rebuilt from scratch in `menuNeedsUpdate(_:)`.** Menu bar apps
|
||||
accumulate bugs from trying to keep dozens of checkmarks in sync with model
|
||||
state. Throwing the menu away and regenerating it from `Preferences` and
|
||||
`WakeController` on each open makes desync structurally impossible, and a menu
|
||||
opens rarely enough that the cost is irrelevant.
|
||||
`WakeController` on each open prevents desync between openings, and a menu
|
||||
opens rarely enough that the cost is irrelevant. State can still change while
|
||||
the menu is open (a timed session expires, you plug in), so the few items whose
|
||||
meaning depends on it — the status line, the Turn On / Turn Off toggle and
|
||||
"Add 30 minutes" — are refreshed live, and the toggle carries the action it
|
||||
displays. A click never does the opposite of its label: if the toggle changed
|
||||
meaning just as you clicked, the click is ignored.
|
||||
|
||||
**`SleepBlocker.apply(_:)` is declarative, not imperative.** You hand it the set
|
||||
of assertions you want held and it reconciles the delta. No `if held { release }`
|
||||
@@ -82,14 +87,27 @@ branching at call sites, and repeated calls are free.
|
||||
### Smart activation semantics
|
||||
|
||||
The tricky part of conditional activation is not the conditions, it's not
|
||||
fighting the user. Wakeful tracks *why* it is on (`.manual` vs `.automatic`):
|
||||
fighting the user. Wakeful tracks *why* it is on (`.manual` vs `.automatic`),
|
||||
and which rules you have overridden:
|
||||
|
||||
- A rule becoming true turns Wakeful on with source `.automatic`.
|
||||
- A rule becoming false turns it off again **only if** source is `.automatic`.
|
||||
Your manual session is never cancelled by a rule.
|
||||
- Manually turning it **off** while a rule wants it on sets a suppression flag,
|
||||
so the app doesn't immediately re-enable itself. The flag clears as soon as
|
||||
the rule stops being satisfied — so unplugging and replugging re-arms it.
|
||||
- When the last rule you haven't overridden stops holding, it turns off again
|
||||
**only if** source is `.automatic`. Your manual session is never cancelled by
|
||||
a rule.
|
||||
- Manually turning it **off** while a rule wants it on overrides that rule, so
|
||||
the app doesn't immediately re-enable itself. The status line names what is
|
||||
paused (e.g. "Sleep allowed — AC rule paused"), and Turn On (and the global
|
||||
shortcut) becomes **Resume Smart Activation**, which hands control back to the
|
||||
rules. Only rules that hold at that moment are overridden; the others stay
|
||||
armed. With the AC and battery rules both on, turning off at your desk and
|
||||
then unplugging lets the battery rule switch Wakeful back on.
|
||||
- Each overridden rule re-arms on its own as soon as it stops being satisfied —
|
||||
so unplugging and replugging re-arms the AC rule even while an external
|
||||
display keeps another rule true. Enabling a rule arms it too.
|
||||
- Starting a manual session overrides the rules that hold at that moment. When
|
||||
a timed session ends, a rule that held throughout stays overridden — so "Keep
|
||||
Awake For 15 minutes" really ends, even on AC — but a rule that became true
|
||||
during the session (you docked halfway through) takes over.
|
||||
|
||||
### Global shortcut
|
||||
|
||||
@@ -143,26 +161,35 @@ Preferences and Launch at Login only behave correctly from the assembled bundle.
|
||||
Click the cup icon in the menu bar.
|
||||
|
||||
- **Turn On / Turn Off** — indefinite session. Same as the global shortcut.
|
||||
While Smart Activation is paused this reads **Resume Smart Activation**.
|
||||
- **Keep Awake For** — 15m / 30m / 1h / 2h / 5h timed session, with a live
|
||||
countdown in the menu bar. Add 30 minutes to extend one in flight.
|
||||
- **Prevent** — pick which assertions to hold. At least one is always held.
|
||||
- **Smart Activation** — auto-enable while on AC, while an external display is
|
||||
connected, or while on battery above 20/50/80% (the battery rule only applies
|
||||
*off* AC, otherwise a charged laptop would satisfy it forever). The submenu
|
||||
footer shows current conditions so you can see why a rule fired.
|
||||
- **Global Shortcut** — off by default; pick a preset to enable it. If the
|
||||
combination is already claimed by another app or a System Settings shortcut,
|
||||
the menu says "unavailable" rather than silently doing nothing. Note that no
|
||||
global shortcut fires while macOS Secure Input is active — i.e. while a
|
||||
password field has focus.
|
||||
*off* AC, otherwise a charged laptop would satisfy it forever). Any display
|
||||
that isn't the built-in panel counts, so the rule also covers a closed laptop
|
||||
driving a monitor, mirroring to a projector, and a desktop Mac's only monitor.
|
||||
The submenu footer shows current conditions so you can see why a rule fired.
|
||||
- **Global Shortcut** — off by default; pick a preset to enable it. Presets that
|
||||
clash with one of macOS's own system shortcuts (screenshots, Spotlight,
|
||||
Mission Control, input sources and the like, in System Settings ▸ Keyboard ▸
|
||||
Keyboard Shortcuts) are marked and can't be picked; the preset you've chosen
|
||||
is checked when you pick it and at each launch. App Shortcuts, Services
|
||||
shortcuts and other apps' shortcuts can't be detected: macOS lets several apps
|
||||
register the same combination and delivers it to all of them, so if a preset
|
||||
already does something elsewhere, pick a different one. No global shortcut
|
||||
fires while macOS Secure Input is active — i.e. while a password field has
|
||||
focus.
|
||||
- **Launch at Login** — install to `/Applications` *first*; the registration
|
||||
records the bundle path, so moving the app afterwards breaks it.
|
||||
- **Turn On at Launch** — combine with Launch at Login for always-awake.
|
||||
|
||||
### Hidden preference
|
||||
|
||||
The toggle and the global shortcut start an indefinite session by default. To
|
||||
make them start a timed one instead:
|
||||
The toggle and the global shortcut start an indefinite session by default
|
||||
(while Smart Activation is paused they resume it instead). To make them start a
|
||||
timed one:
|
||||
|
||||
```sh
|
||||
defaults write com.github.wakeful.Wakeful defaultDurationMinutes -int 60
|
||||
|
||||
Reference in New Issue
Block a user