From 409bd8f135f6c3eeffccabd7c1bf3fd241e22af0 Mon Sep 17 00:00:00 2001 From: ElMoorish Date: Tue, 17 Mar 2026 05:30:42 +0000 Subject: [PATCH 1/2] Fix hardware mute toggle and robust JSON parsing --- engine.py | 42 ++++++++++++++++++++------------- main.js | 11 +++++++-- package-lock.json | 60 ++++++++++++++++++++++++++++++++++++----------- requirements.txt | 1 + test_keys.py | 43 +++++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 32 deletions(-) create mode 100644 test_keys.py diff --git a/engine.py b/engine.py index 6c897b6..0a0c506 100644 --- a/engine.py +++ b/engine.py @@ -29,7 +29,9 @@ def __init__(self): self.lock = threading.Lock() self.is_recording = False + print("DEBUG: Initializing PyAudio...", file=sys.stderr) self.pa = pyaudio.PyAudio() + print("DEBUG: PyAudio initialized.", file=sys.stderr) self.last_audio_hash = None self.last_sound_time = 0 self.last_typed_text = "" @@ -38,13 +40,16 @@ def __init__(self): self.pre_mute_state = False # Audio Feedback + print("DEBUG: Initializing pygame mixer...", file=sys.stderr) pygame.mixer.init() try: # Use relative paths for portability and privacy base_dir = os.path.dirname(os.path.abspath(__file__)) self.start_snd = pygame.mixer.Sound(os.path.join(base_dir, "start.mp3")) self.stop_snd = pygame.mixer.Sound(os.path.join(base_dir, "stop.mp3")) - except: + print("DEBUG: Pygame mixer and sounds loaded.", file=sys.stderr) + except Exception as e: + print(f"DEBUG: Pygame load failed: {e}", file=sys.stderr) self.start_snd = self.stop_snd = None self.model = self._load_model() @@ -52,23 +57,24 @@ def __init__(self): def mute_pc(self, mute=True): if sys.platform != 'win32': return - # --- NEW WAY: Hardware Virtual Keys --- - # 0xAD is VK_VOLUME_MUTE. We simulate a physical key press. - # This bypasses all library/COM/object errors because it's at the OS input level. - VK_VOLUME_MUTE = 0xAD - try: - # We use keybd_event to send the MUTE command directly to the Windows shell - # This is exactly what happens when you press the 'Mute' button on your keyboard. - ctypes.windll.user32.keybd_event(VK_VOLUME_MUTE, 0, 0, 0) # Press - ctypes.windll.user32.keybd_event(VK_VOLUME_MUTE, 0, 2, 0) # Release + CoInitialize() + speakers = AudioUtilities.GetSpeakers() + if speakers: + volume = speakers.EndpointVolume + # Absolute mute + volume.SetMute(1 if mute else 0, None) + # state = "ON" if mute else "OFF" + # print(f"DEBUG: Absolute Mute (pycaw): {state}", file=sys.stderr) - if mute: - print("DEBUG: Hardware Mute Toggled (ON)", file=sys.stderr) - else: - print("DEBUG: Hardware Mute Toggled (OFF)", file=sys.stderr) + # Clean up COM + CoUninitialize() except Exception as e: - print(f"DEBUG: Hardware Mute failed: {e}", file=sys.stderr) + print(f"DEBUG: Absolute Mute failed: {e}", file=sys.stderr) + # Fallback to older toggle way if pycaw fails + VK_VOLUME_MUTE = 0xAD + ctypes.windll.user32.keybd_event(VK_VOLUME_MUTE, 0, 0, 0) + ctypes.windll.user32.keybd_event(VK_VOLUME_MUTE, 0, 2, 0) def _load_model(self): self.log("status", {"text": f"Pace AI Warming Up ({self.model_size})..."}) @@ -82,10 +88,14 @@ def _load_model(self): if not os.path.exists(path): os.makedirs(path, exist_ok=True) - + + print(f"DEBUG: Loading Whisper model from {path}...", file=sys.stderr) model = WhisperModel(self.model_size, device="cpu", compute_type="int8", download_root=path, cpu_threads=4) + print("DEBUG: Whisper model object created.", file=sys.stderr) # Warmup + print("DEBUG: Warming up model...", file=sys.stderr) list(model.transcribe(np.zeros(16000, dtype=np.float32), beam_size=1)) + print("DEBUG: Model warmup complete.", file=sys.stderr) self.log("engine_ready", {"model": self.model_size}) return model diff --git a/main.js b/main.js index 389606b..088c2ba 100644 --- a/main.js +++ b/main.js @@ -220,8 +220,13 @@ function startPythonEngine() { console.error(`Python Engine Error: ${data}`); }); + let stdoutBuffer = ''; pythonProcess.stdout.on('data', (data) => { - data.toString().split('\n').forEach(line => { + stdoutBuffer += data.toString(); + let lines = stdoutBuffer.split('\n'); + stdoutBuffer = lines.pop(); // Keep the last incomplete line + + lines.forEach(line => { if (!line.trim()) return; try { const json = JSON.parse(line); @@ -235,7 +240,9 @@ function startPythonEngine() { } if (mainWindow) mainWindow.webContents.send('engine-msg', json); - } catch (e) {} + } catch (e) { + console.error('JSON Parse Error:', e, 'on line:', line); + } }); }); diff --git a/package-lock.json b/package-lock.json index 2823072..c728ee9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -675,7 +675,6 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -830,6 +829,7 @@ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", @@ -849,6 +849,7 @@ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -871,6 +872,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -886,7 +888,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", @@ -894,6 +897,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -995,6 +999,7 @@ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -1367,6 +1372,7 @@ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -1467,6 +1473,7 @@ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -1480,6 +1487,7 @@ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -1657,7 +1665,6 @@ "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "builder-util": "24.13.1", @@ -1842,6 +1849,7 @@ "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "archiver": "^5.3.1", @@ -1855,6 +1863,7 @@ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1870,6 +1879,7 @@ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -1883,6 +1893,7 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -2216,7 +2227,8 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fs-extra": { "version": "8.1.0", @@ -2712,7 +2724,8 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/isbinaryfile": { "version": "5.0.7", @@ -2849,6 +2862,7 @@ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "readable-stream": "^2.0.5" }, @@ -2862,6 +2876,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2877,7 +2892,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", @@ -2885,6 +2901,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -2901,35 +2918,40 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lowercase-keys": { "version": "2.0.0", @@ -3118,6 +3140,7 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3251,7 +3274,8 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/progress": { "version": "2.0.3", @@ -3335,6 +3359,7 @@ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3350,6 +3375,7 @@ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "minimatch": "^5.1.0" } @@ -3432,7 +3458,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -3631,6 +3658,7 @@ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -3744,6 +3772,7 @@ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -3901,7 +3930,8 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/verror": { "version": "1.10.1", @@ -4052,6 +4082,7 @@ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", @@ -4067,6 +4098,7 @@ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", diff --git a/requirements.txt b/requirements.txt index e7bfc8f..c08d672 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ PyAudio numpy keyboard pygame +pyperclip \ No newline at end of file diff --git a/test_keys.py b/test_keys.py new file mode 100644 index 0000000..f0bbc6a --- /dev/null +++ b/test_keys.py @@ -0,0 +1,43 @@ + +import ctypes +import time +import sys + +def test_keys(): + # Windows Virtual Key Codes + VK_CONTROL = 0x11 + VK_MENU = 0x12 # ALT key + + def is_pressed(vk): + # Direct Windows API call for physical key state + # 0x8000 is the bitmask for "currently held down" + return (ctypes.windll.user32.GetAsyncKeyState(vk) & 0x8000) != 0 + + print("Testing GetAsyncKeyState for CTRL + ALT...") + print("Press CTRL+ALT now (or ESC to exit).") + + try: + while True: + ctrl = is_pressed(VK_CONTROL) + alt = is_pressed(VK_MENU) + esc = is_pressed(0x1B) # VK_ESCAPE + + if ctrl and alt: + print("\r[TRIGGERED] CTRL + ALT detected! ", end="") + elif ctrl: + print("\r[PARTIAL] CTRL detected... ", end="") + elif alt: + print("\r[PARTIAL] ALT detected... ", end="") + else: + print("\r[WAITING] No keys detected... ", end="") + + if esc: + print("\nExiting test.") + break + + time.sleep(0.05) + except KeyboardInterrupt: + print("\nTest interrupted.") + +if __name__ == "__main__": + test_keys() From e7d10b639f25509d0b068100103a1fbfde71a31a Mon Sep 17 00:00:00 2001 From: ElMoorish Date: Tue, 17 Mar 2026 05:40:42 +0000 Subject: [PATCH 2/2] Cleanup telemetry and test files before PR --- engine.py | 13 ++----------- test_keys.py | 43 ------------------------------------------- 2 files changed, 2 insertions(+), 54 deletions(-) delete mode 100644 test_keys.py diff --git a/engine.py b/engine.py index 0a0c506..b79a6b2 100644 --- a/engine.py +++ b/engine.py @@ -29,9 +29,7 @@ def __init__(self): self.lock = threading.Lock() self.is_recording = False - print("DEBUG: Initializing PyAudio...", file=sys.stderr) self.pa = pyaudio.PyAudio() - print("DEBUG: PyAudio initialized.", file=sys.stderr) self.last_audio_hash = None self.last_sound_time = 0 self.last_typed_text = "" @@ -40,16 +38,13 @@ def __init__(self): self.pre_mute_state = False # Audio Feedback - print("DEBUG: Initializing pygame mixer...", file=sys.stderr) pygame.mixer.init() try: # Use relative paths for portability and privacy base_dir = os.path.dirname(os.path.abspath(__file__)) self.start_snd = pygame.mixer.Sound(os.path.join(base_dir, "start.mp3")) self.stop_snd = pygame.mixer.Sound(os.path.join(base_dir, "stop.mp3")) - print("DEBUG: Pygame mixer and sounds loaded.", file=sys.stderr) - except Exception as e: - print(f"DEBUG: Pygame load failed: {e}", file=sys.stderr) + except: self.start_snd = self.stop_snd = None self.model = self._load_model() @@ -88,14 +83,10 @@ def _load_model(self): if not os.path.exists(path): os.makedirs(path, exist_ok=True) - - print(f"DEBUG: Loading Whisper model from {path}...", file=sys.stderr) + model = WhisperModel(self.model_size, device="cpu", compute_type="int8", download_root=path, cpu_threads=4) - print("DEBUG: Whisper model object created.", file=sys.stderr) # Warmup - print("DEBUG: Warming up model...", file=sys.stderr) list(model.transcribe(np.zeros(16000, dtype=np.float32), beam_size=1)) - print("DEBUG: Model warmup complete.", file=sys.stderr) self.log("engine_ready", {"model": self.model_size}) return model diff --git a/test_keys.py b/test_keys.py deleted file mode 100644 index f0bbc6a..0000000 --- a/test_keys.py +++ /dev/null @@ -1,43 +0,0 @@ - -import ctypes -import time -import sys - -def test_keys(): - # Windows Virtual Key Codes - VK_CONTROL = 0x11 - VK_MENU = 0x12 # ALT key - - def is_pressed(vk): - # Direct Windows API call for physical key state - # 0x8000 is the bitmask for "currently held down" - return (ctypes.windll.user32.GetAsyncKeyState(vk) & 0x8000) != 0 - - print("Testing GetAsyncKeyState for CTRL + ALT...") - print("Press CTRL+ALT now (or ESC to exit).") - - try: - while True: - ctrl = is_pressed(VK_CONTROL) - alt = is_pressed(VK_MENU) - esc = is_pressed(0x1B) # VK_ESCAPE - - if ctrl and alt: - print("\r[TRIGGERED] CTRL + ALT detected! ", end="") - elif ctrl: - print("\r[PARTIAL] CTRL detected... ", end="") - elif alt: - print("\r[PARTIAL] ALT detected... ", end="") - else: - print("\r[WAITING] No keys detected... ", end="") - - if esc: - print("\nExiting test.") - break - - time.sleep(0.05) - except KeyboardInterrupt: - print("\nTest interrupted.") - -if __name__ == "__main__": - test_keys()