Replace OpenCode harness with Hermes, remove workspace dependency, and move vocabulary/corrections to app folder
This commit is contained in:
@@ -29,6 +29,44 @@ from CoreFoundation import (
|
||||
kCFRunLoopCommonModes,
|
||||
)
|
||||
|
||||
|
||||
def disable_app_nap(reason: str = "VoiceAgent Hold-to-Talk Event Tap"):
|
||||
"""Prevent macOS App Nap from throttling thread execution and timing out event taps."""
|
||||
try:
|
||||
from Foundation import (
|
||||
NSActivityIdleSystemSleepDisabled,
|
||||
NSActivityLatencyCritical,
|
||||
NSActivityUserInitiated,
|
||||
NSProcessInfo,
|
||||
)
|
||||
|
||||
options = (
|
||||
NSActivityUserInitiated
|
||||
| NSActivityIdleSystemSleepDisabled
|
||||
| NSActivityLatencyCritical
|
||||
)
|
||||
token = NSProcessInfo.processInfo().beginActivityWithOptions_reason_(
|
||||
options, reason
|
||||
)
|
||||
logger.info("Disabled macOS App Nap for low-latency hotkey monitoring.")
|
||||
return token
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not disable App Nap via Foundation: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def enable_app_nap(token):
|
||||
"""Restore normal App Nap power management for the given activity token."""
|
||||
if token is None:
|
||||
return
|
||||
try:
|
||||
from Foundation import NSProcessInfo
|
||||
|
||||
NSProcessInfo.processInfo().endActivity_(token)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error ending App Nap activity: {e}")
|
||||
|
||||
|
||||
# (keycode, modifier mask) for the keys worth holding. Modifier keycodes arrive
|
||||
# on flagsChanged events, so one handler covers all of them.
|
||||
HOLD_KEYS: dict[str, tuple[int, int]] = {
|
||||
@@ -73,15 +111,20 @@ class HoldKeyMonitor:
|
||||
self._down = False
|
||||
self._ready = threading.Event()
|
||||
self._started_ok = False
|
||||
self._activity_token = None
|
||||
|
||||
def start(self) -> bool:
|
||||
"""Begin watching. Returns False if the tap could not be created."""
|
||||
self._activity_token = disable_app_nap("VoiceAgent HoldKeyMonitor")
|
||||
self._thread = threading.Thread(target=self._run, name="hold-key-tap", daemon=True)
|
||||
self._thread.start()
|
||||
self._ready.wait(timeout=5)
|
||||
return self._started_ok
|
||||
|
||||
def stop(self):
|
||||
if self._activity_token is not None:
|
||||
enable_app_nap(self._activity_token)
|
||||
self._activity_token = None
|
||||
if self._runloop is not None:
|
||||
CFRunLoopStop(self._runloop)
|
||||
self._runloop = None
|
||||
@@ -118,6 +161,9 @@ class HoldKeyMonitor:
|
||||
Quartz.kCGEventTapDisabledByTimeout,
|
||||
Quartz.kCGEventTapDisabledByUserInput,
|
||||
):
|
||||
logger.warning(
|
||||
f"Quartz event tap disabled by OS (type={event_type}); re-enabling tap."
|
||||
)
|
||||
if self._tap is not None:
|
||||
Quartz.CGEventTapEnable(self._tap, True)
|
||||
return event
|
||||
@@ -134,3 +180,4 @@ class HoldKeyMonitor:
|
||||
except Exception as e: # never let an exception cross back into C
|
||||
logger.debug(f"Hold-key tap callback error: {e}")
|
||||
return event
|
||||
|
||||
|
||||
Reference in New Issue
Block a user