The Apple half of settings profiles (design/client-settings-profiles.md §4) starts with the thing the desktop shells got for free from the shared Rust core: a model, and one place that resolves it. `SettingsOverlay`/`StreamProfile`/`ProfileCatalog` mirror `pf-client-core::profiles` field for field, unknown-key carry-through included — an older build that opens a profile a newer one wrote must not gut it on save. The catalog lives in the App Group suite beside the saved hosts, because the things that point at it are fields on the host record: `StoredHost` gains `profileID` and `pinnedProfileIDs`, both optional and appended last, because that JSON is a widget contract. `EffectiveSettings` is the resolution — globals with the session's overlay on top, computed once at connect and latched in `SessionSettings` for the rest of it. That latch is the point. Ten sites across the app AND the kit read `UserDefaults` directly mid-session (the presenter's priority and VRR, the vsync flip, the match-window follower, the scroll sign, the touch model, the mouse model, 4:4:4, the audio endpoints); a profile that reached some of them and not others would be worse than no feature at all. They now read the latch, which off-session is the plain globals — byte for byte what they saw before. The deep-link grammar grows up with it: `DeepLink` becomes a full port of `deeplink.rs` — routes, host-ref forms, `fp`/`host` recovery, one-off `profile`, and every refusal code — and the Swift suite now runs `clients/shared/deeplink-vectors.json` itself, read from the source tree so there is no second copy to drift. All 44 cases green. The router refuses what it can't honor by name: a profile that doesn't exist, an ambiguous one, a fingerprint that contradicts the pin. A shortcut that streams with the wrong settings is worse than one that explains itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
54 lines
2.8 KiB
Swift
54 lines
2.8 KiB
Swift
// Location-based modifier mapping — which Windows VK each PHYSICAL modifier position forwards to
|
|
// the host. A Mac keyboard's bottom row is `⌃ Control / ⌥ Option / ⌘ Command / space`; a Windows
|
|
// keyboard's is `Ctrl / ⊞ Super / Alt / space`. So the key NEAREST the space bar is Command on a
|
|
// Mac but Alt on Windows, and the next one out is Option on a Mac but the Windows key. A user who
|
|
// grew up on Windows reaches for Alt where the Mac has Command; this setting lets them get that
|
|
// muscle memory back WITHOUT relabelling keycaps — it remaps by physical position, not by a blunt
|
|
// "swap these two VKs" that would also drag Control/Shift or lose the left/right distinction.
|
|
//
|
|
// The model: keep the physical detection (which side, which key) exactly as the OS reports it, and
|
|
// swap only the Alt-vs-Super ROLE between the Option and Command keys, per side. So under `.windows`
|
|
// the ⌘ position emits Alt (VK_L/RMENU) and the ⌥ position emits the Windows key (VK_L/RWIN), while
|
|
// left stays left and right stays right. Control and Shift are in the same place on both keyboards,
|
|
// so they never move. Lives in PunktfunkShared because both the Settings UI (PunktfunkClient) and
|
|
// the wire-boundary remap (`InputCapture.applyModifierLayout`, PunktfunkKit) resolve it.
|
|
|
|
import Foundation
|
|
|
|
/// How the physical ⌥ Option / ⌘ Command keys map to host modifiers. The raw values are stable on
|
|
/// disk — rename the cases freely, never the strings.
|
|
public enum ModifierLayout: String, CaseIterable, Sendable {
|
|
/// Apple positions (default): ⌥ Option → Alt, ⌘ Command → Super/Windows. The current behaviour.
|
|
case mac
|
|
/// Windows positions: the key nearest the space bar (⌘ Command) → Alt, the next one (⌥ Option)
|
|
/// → the Windows/Super key. Side (left/right) is preserved.
|
|
case windows
|
|
|
|
/// User-facing label (Settings picker).
|
|
public var label: String {
|
|
switch self {
|
|
case .mac: return "Mac (⌥ Alt · ⌘ Super)"
|
|
case .windows: return "Windows (⌘ Alt · ⌥ Super)"
|
|
}
|
|
}
|
|
|
|
/// A one-line explanation for the setting's footer.
|
|
public var detail: String {
|
|
switch self {
|
|
case .mac:
|
|
return "The ⌥ Option key sends Alt and the ⌘ Command key sends the Windows key — the Apple layout."
|
|
case .windows:
|
|
return "The key nearest the space bar sends Alt and the next one sends the Windows key, matching a PC keyboard. Client shortcuts (⌘⎋ and friends) still use the physical ⌘ key."
|
|
}
|
|
}
|
|
|
|
/// The persisted layout (default `.mac` when unset).
|
|
public static var current: ModifierLayout {
|
|
guard let raw = SessionSettings.active?.modifierLayout
|
|
?? UserDefaults.standard.string(forKey: DefaultsKey.modifierLayout) else {
|
|
return .mac
|
|
}
|
|
return ModifierLayout(rawValue: raw) ?? .mac
|
|
}
|
|
}
|