110 lines
3.5 KiB
Swift
110 lines
3.5 KiB
Swift
import CapswitchHID
|
|
import Foundation
|
|
|
|
enum HIDRemapError: LocalizedError {
|
|
case failure(Int32)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case let .failure(code):
|
|
switch code {
|
|
case 1:
|
|
return "Not enough memory to manage keyboard mappings."
|
|
case 2:
|
|
return "Cannot access IOKit keyboard services. Check Capswitch's macOS privacy permissions."
|
|
case 3:
|
|
return "No HID keyboard services found. Connect a keyboard and retry."
|
|
case 4:
|
|
return "An existing keyboard mapping has an unsupported format. No unrelated mappings were changed."
|
|
case 5:
|
|
return "Caps Lock or F19 already has a conflicting mapping. Capswitch will not overwrite it."
|
|
case 6:
|
|
return "macOS rejected the native Caps → F19 mapping for a keyboard."
|
|
case 7:
|
|
return "A keyboard did not confirm the Caps → F19 mapping."
|
|
case 8:
|
|
return "Could not identify a keyboard service safely."
|
|
case 9:
|
|
return "Cannot persist keyboard remapping recovery data. Capswitch will not accept an untracked remap."
|
|
default:
|
|
return "Native IOKit keyboard remapping failed (code \(code))."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Owns the native IOKit remapping manager inside Capswitch itself.
|
|
/// No subprocess, hidutil, privileged helper, driver or third-party library.
|
|
final class HIDRemappingController {
|
|
private let manager: UnsafeMutableRawPointer?
|
|
|
|
private(set) var isInstalled = false
|
|
private(set) var keyboardServiceCount: UInt32 = 0
|
|
private(set) var mappedServiceCount: UInt32 = 0
|
|
|
|
init() {
|
|
manager = CapswitchRemapperCreate()
|
|
}
|
|
|
|
deinit {
|
|
if let manager {
|
|
CapswitchRemapperDestroy(manager)
|
|
}
|
|
}
|
|
|
|
func ensureInstalled() throws {
|
|
guard let manager else {
|
|
throw HIDRemapError.failure(1)
|
|
}
|
|
|
|
var keyboardCount: UInt32 = 0
|
|
var mappedCount: UInt32 = 0
|
|
let status = CapswitchRemapperApply(manager, &keyboardCount, &mappedCount)
|
|
|
|
keyboardServiceCount = keyboardCount
|
|
mappedServiceCount = mappedCount
|
|
isInstalled = status == 0 && keyboardCount > 0
|
|
&& mappedCount == keyboardCount
|
|
|
|
guard status == 0, isInstalled else {
|
|
throw HIDRemapError.failure(status == 0 ? 7 : status)
|
|
}
|
|
}
|
|
|
|
func reapplyIfOwned() throws {
|
|
// Reconciliation is idempotent: the native layer checks every
|
|
// keyboard and only installs missing mappings it can safely own.
|
|
try ensureInstalled()
|
|
}
|
|
|
|
func inspect() throws {
|
|
guard let manager else {
|
|
throw HIDRemapError.failure(1)
|
|
}
|
|
|
|
var keyboardCount: UInt32 = 0
|
|
var mappedCount: UInt32 = 0
|
|
let status = CapswitchRemapperInspect(manager, &keyboardCount, &mappedCount)
|
|
keyboardServiceCount = keyboardCount
|
|
mappedServiceCount = mappedCount
|
|
|
|
guard status == 0 else {
|
|
throw HIDRemapError.failure(status)
|
|
}
|
|
}
|
|
|
|
func restoreIfOwned() throws {
|
|
guard let manager else {
|
|
throw HIDRemapError.failure(1)
|
|
}
|
|
|
|
let status = CapswitchRemapperRestore(manager)
|
|
guard status == 0 else {
|
|
throw HIDRemapError.failure(status)
|
|
}
|
|
|
|
isInstalled = false
|
|
keyboardServiceCount = 0
|
|
mappedServiceCount = 0
|
|
}
|
|
}
|