README 및 install.sh, razer-tilt.swift 파일 업데이트: 권한 설정 및 자동 재시작 로직 개선
This commit is contained in:
+50
-11
@@ -9,8 +9,15 @@ func log(_ message: String) {
|
||||
fflush(stdout)
|
||||
}
|
||||
|
||||
struct ReportSource: Hashable {
|
||||
let device: Int
|
||||
let reportID: UInt32
|
||||
}
|
||||
|
||||
final class State {
|
||||
var buffers: [IOHIDDevice: UnsafeMutablePointer<UInt8>] = [:]
|
||||
var lastBits: [ReportSource: UInt8] = [:]
|
||||
var lastFired: [UInt8: TimeInterval] = [:]
|
||||
var opened = false
|
||||
var lastStatus = ""
|
||||
}
|
||||
@@ -30,16 +37,25 @@ func sendCommandKey(_ keyCode: CGKeyCode) {
|
||||
up.post(tap: .cgSessionEventTap)
|
||||
}
|
||||
|
||||
let tiltMask: UInt8 = 0x60
|
||||
// Ignore a second press of the same tilt within this window (switch bounce).
|
||||
let debounceInterval: TimeInterval = 0.12
|
||||
|
||||
// Preserve the supplied Razer report layout and Command+[ / Command+] mapping.
|
||||
// The mouse repeats the tilt bit in every report while held, so fire only on the press edge.
|
||||
let reportCallback: IOHIDReportCallback = {
|
||||
_, result, _, _, _, report, reportLength in
|
||||
guard result == kIOReturnSuccess, reportLength >= 1 else { return }
|
||||
let value = report[0]
|
||||
if value & 0x20 != 0 {
|
||||
sendCommandKey(33)
|
||||
}
|
||||
if value & 0x40 != 0 {
|
||||
sendCommandKey(30)
|
||||
context, result, sender, _, reportID, report, reportLength in
|
||||
guard result == kIOReturnSuccess, reportLength >= 1, let context = context else { return }
|
||||
let state = Unmanaged<State>.fromOpaque(context).takeUnretainedValue()
|
||||
let source = ReportSource(device: Int(bitPattern: sender), reportID: reportID)
|
||||
let value = report[0] & tiltMask
|
||||
let pressed = value & ~(state.lastBits[source] ?? 0)
|
||||
state.lastBits[source] = value
|
||||
let now = ProcessInfo.processInfo.systemUptime
|
||||
for (mask, keyCode) in [(UInt8(0x20), CGKeyCode(33)), (UInt8(0x40), CGKeyCode(30))] where pressed & mask != 0 {
|
||||
if let last = state.lastFired[mask], now - last < debounceInterval { continue }
|
||||
state.lastFired[mask] = now
|
||||
sendCommandKey(keyCode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +76,8 @@ let deviceCallback: IOHIDDeviceCallback = { context, result, _, device in
|
||||
let removalCallback: IOHIDDeviceCallback = { context, _, _, device in
|
||||
guard let context = context else { return }
|
||||
let state = Unmanaged<State>.fromOpaque(context).takeUnretainedValue()
|
||||
let deviceKey = Int(bitPattern: Unmanaged.passUnretained(device).toOpaque())
|
||||
state.lastBits = state.lastBits.filter { $0.key.device != deviceKey }
|
||||
if let buffer = state.buffers.removeValue(forKey: device) {
|
||||
IOHIDDeviceRegisterInputReportCallback(device, buffer, 0, nil, nil)
|
||||
buffer.deallocate()
|
||||
@@ -77,14 +95,22 @@ IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(), CFRunLoopMode.de
|
||||
log("Razer Tilt Mapper started. LEFT -> Command+[; RIGHT -> Command+]")
|
||||
|
||||
// macOS presents its own permission dialogs. The user grants access in Settings.
|
||||
if !AXIsProcessTrusted() {
|
||||
// Launchd restarts this app while permissions are missing, so prompt only once per build.
|
||||
let buildStamp = (try? FileManager.default.attributesOfItem(atPath: CommandLine.arguments[0])[.modificationDate] as? Date)
|
||||
.map { String($0.timeIntervalSince1970) } ?? ""
|
||||
if !AXIsProcessTrusted() && UserDefaults.standard.string(forKey: "promptedBuild") != buildStamp {
|
||||
let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true]
|
||||
_ = AXIsProcessTrustedWithOptions(options as CFDictionary)
|
||||
UserDefaults.standard.set(buildStamp, forKey: "promptedBuild")
|
||||
}
|
||||
if IOHIDCheckAccess(kIOHIDRequestTypeListenEvent) == kIOHIDAccessTypeUnknown {
|
||||
_ = IOHIDRequestAccess(kIOHIDRequestTypeListenEvent)
|
||||
}
|
||||
|
||||
func accessGranted() -> Bool {
|
||||
IOHIDCheckAccess(kIOHIDRequestTypeListenEvent) == kIOHIDAccessTypeGranted && CGPreflightPostEventAccess()
|
||||
}
|
||||
|
||||
func updateAccess() {
|
||||
let inputAllowed = IOHIDCheckAccess(kIOHIDRequestTypeListenEvent) == kIOHIDAccessTypeGranted
|
||||
let outputAllowed = CGPreflightPostEventAccess()
|
||||
@@ -102,6 +128,19 @@ func updateAccess() {
|
||||
}
|
||||
|
||||
updateAccess()
|
||||
// Recheck after the initial permission grant without requiring a manual restart.
|
||||
let accessTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in updateAccess() }
|
||||
// A running process may not see a new grant, so exit while access is missing and let
|
||||
// launchd (KeepAlive) start a fresh process that picks it up.
|
||||
let accessTimer: Timer? = accessGranted() ? nil : {
|
||||
let started = Date()
|
||||
return Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in
|
||||
if accessGranted() {
|
||||
log("Permissions granted; restarting to apply them.")
|
||||
exit(0)
|
||||
}
|
||||
if Date().timeIntervalSince(started) >= 20 {
|
||||
log("Permissions still missing; restarting to recheck.")
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}()
|
||||
CFRunLoopRun()
|
||||
|
||||
Reference in New Issue
Block a user