0

Goal

I'm trying to build a minimal working system where pressing a key in AutoHotkey (AHK) sends a message to a Chrome Extension, via a Python native messaging host.

The architecture is:

AHK → Named Pipe → Python (native host) → Chrome Extension (Manifest V3)

The end goal is simple: pressing a hotkey (like NumpadAdd) should trigger an event inside my extension.


What's Working

  • AHK opens the named pipe \\.\pipe\NativeEventPipe and writes JSON.
  • Python (native host) receives the AHK message via win32pipe.ReadFile(...).
  • ✅ Python logs the message and calls sys.stdout.buffer.write(...) + flush() to send to Chrome.
  • Chrome connects to the host and sends a handshake ({ cmd: "start", attempt: 1 }).
  • Chrome never receives the AHK event. onMessage never fires after the handshake.

Everything below the native messaging boundary works — the AHK input is reaching Python. But Chrome doesn’t see the message sent from the native host.


Python Snippet (Native Host)

def send_message(message):
    try:
        encoded = json.dumps(message).encode("utf-8")
        sys.stdout.buffer.write(struct.pack("I", len(encoded)))
        sys.stdout.buffer.write(encoded)
        sys.stdout.buffer.flush()
        return True
    except Exception as e:
        # logs error
        return False

Extension Snippet

port = chrome.runtime.connectNative("com.nativebridge.test");

port.onMessage.addListener((msg) => {
  console.log("✅ [SW] AHK Event:", msg);
});

What I’m Looking For

Can someone help me complete this system successfully?
I’m not committed to named pipes — I just want a minimal working solution where:

  • AHK triggers an event
  • That event is seen by the Chrome extension
  • Native messaging is used in some form (pipe/file/stdin/etc.)

Also:

  • ❓ Is my use of sys.stdout.buffer.write(...) correct in this IPC model?
  • ❓ Is the pipe model fundamentally flawed when combined with stdio native messaging?
  • ❓ Would a cleaner design (like invoking a short-lived subprocess from AHK) be more reliable?

Files & Repo

I've constructed a mostly minimal version of the project and made it public on github: https://github.com/ModifiedFootage/AhkNativeBridge


System

  • Windows 11 Pro
  • Brave Browser (Chromium)
  • Chrome Extension (Manifest V3, service worker)
  • AutoHotkey v1
  • Python 3.11
  • Using win32pipe, win32file for pipe access

Thanks in advance for any help on either finishing the system or improving the architecture.

1
  • Chrome doesn't connect to the already running host, it starts a new one. I also wonder if the extension needs to send something to the port first in order for onMessage to trigger and maybe you'll need to do such ping-pong for every message cycle. FWIW you can use commands API to define global hotkeys intercepted by the extension directly. Both physical and AHK-synthesized keys would work. Commented Apr 21 at 9:43

1 Answer 1

0

I worked it out! It was rather simple.

launch.bat

@echo off

:: Assumes native_bridge.py and the 'venv' folder are in the same directory as this batch file.

:: Path to venv python relative to this batch file
set VENV_PYTHON="%~dp0venv\Scripts\python.exe"

:: Path to the python script relative to this batch file
set SCRIPT_PATH="%~dp0native_bridge.py"

:: Define log file paths relative to this batch file's location
set STDOUT_LOG="%~dp0native_bridge_stdout.log"
set STDERR_LOG="%~dp0native_bridge_stderr.log"

:: Execute the script using the venv's python, redirecting output
%VENV_PYTHON% %SCRIPT_PATH% > %STDOUT_LOG% 2> %STDERR_LOG%

com.nativebridge.test.json

{
  "name": "com.nativebridge.test",
  "description": "Persistent event bridge",
  "path": "C:\\NativeBridgeTest\\native_host\\launch.bat",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://fedjpkbjlhmmnaipkljgbhfofpnmbamc/"
  ]
}

As you can see the manifest is launching launch.bat. Great. My AHK script sends messages down the pipe, they arrive in native_bridge.py but never reach the browser? Why? Because I'm redirecting stdout of native_bridge.py for logging... Well whatever I guess I worked it out. I'll leave this repo public in it's fixed state in case anyone wants to copy it in future.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.