Inconsistent delay when using RegisterHotkey and PeekMessagew In Go

28 views Asked by At

I'm trying to write a simple Go program that will listen for global windows hotkeys while in the background and send API calls when specific ones have been pressed. This is my first time learning how to work with Windows messages so this is probably just me not understanding how they work.

My calls to PeekMessageW often return 0 even though hotkeys are being pressed, and then after a minute or so suddenly returns them all at once. Is this just expected behaviour?

I'm doing everything on the same goroutine. First I create a new window and save the HWND like this:

func createWindow(
    user32 *syscall.DLL,
    dwExStyle uint32,
    lpClassName, lpWindowName *uint16,
    dwStyle uint32,
    x, y, nWidth, nHeight int,
    hWndParent, hMenu, hInstance uintptr,
    lpParam unsafe.Pointer,
) uintptr {
    procCreateWindowEx := user32.MustFindProc("CreateWindowExW")

    ret, _, _ := procCreateWindowEx.Call(
        uintptr(dwExStyle),
        uintptr(unsafe.Pointer(lpClassName)),
        uintptr(unsafe.Pointer(lpWindowName)),
        uintptr(dwStyle),
        uintptr(x),
        uintptr(y),
        uintptr(nWidth),
        uintptr(nHeight),
        hWndParent,
        hMenu,
        hInstance,
        uintptr(lpParam),
    )
    return ret
}

func GiveSimpleWindowPls(user32 *syscall.DLL) uintptr {
    var hwnd uintptr
    className, _ := syscall.UTF16PtrFromString("STATIC")
    windowName, _ := syscall.UTF16PtrFromString("Simple Window")

    hwnd = createWindow(
        user32,
        0,
        className,
        windowName,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        nil,
    )

    if hwnd != 0 {
        fmt.Println("HWND:", hwnd)
    } else {
        fmt.Println("Could not create window")
    }
    return hwnd
}

I then register my hotkeys:

func Register(user32 *syscall.DLL, hwnd uintptr) (map[int]*Hotkey, error) {
    reghotkey := user32.MustFindProc("RegisterHotKey")

    // Hotkeys to listen to:
    // (hardcoded for now)
    keys := map[int]*Hotkey{
        6:  {6, ModAlt + ModCtrl + ModShift, '6'},
        7:  {7, ModAlt + ModCtrl + ModShift, '7'},
        8:  {8, ModAlt + ModCtrl + ModShift, '8'},
        9:  {9, ModAlt + ModCtrl + ModShift, '9'},
        10: {10, ModAlt + ModCtrl + ModShift, '0'},
    }

    // Register hotkeys:
    for _, v := range keys {
        r1, _, err := reghotkey.Call(hwnd, uintptr(v.Id), uintptr(v.Modifiers), uintptr(v.KeyCode))
        if r1 != 1 {
            return nil, fmt.Errorf("error registering hotkey %#v: %w", v, err)
        }
    }
    return keys, nil
}

And finally call PeekMessageW in a loop:

const WM_HOTKEY = 0x0312

func Listen(keys map[int]*Hotkey, switcher Switcher, hwnd uintptr) {
    peekmsg := user32.MustFindProc("PeekMessageW")

    for {
        var msg = &MSG{}
        a, _, _ := peekmsg.Call(uintptr(unsafe.Pointer(msg)), hwnd, WM_HOTKEY, WM_HOTKEY, 1)
        fmt.Printf("%#v %d \n", msg, a)

        if a == 0 {
            time.Sleep(time.Millisecond * 50)
            continue
        }

        if key, ok := keys[int(msg.WPARAM)]; ok {
            fmt.Println("Hotkey was pressed:", key)

            switcher.Switch(key.Id) // for now this just does nothing and returns
        }
    }
}

And at first this seems to work perfectly, but every minute or so PeekMessageW starts to only return 0 even though I am pressing my hotkeys. Then after a while, it returns all the messages one after the other, as if the queue had congested for a while and finally gets released.

Am I wrong in expecting to be able to peek WM_HOTKEY messages immediately (or at least within a couple seconds) after the hotkey is pressed? The program sometimes stops receiving these messages for up to a minute at a time, and then suddenly processes them all at once. Is the queue being blocked by some other process I have running that Windows gives priority?

This is my first time trying to figure out how this works, but I've spent hours searching for a solution online and can't see where I'm going wrong.

1

There are 1 answers

0
Jakub Kwak On

After several red herrings I finally found the solution to the problem. I needed to call runtime.LockOsThread() before everything to lock my goroutine to the current system thread, as the hotkey messages are thread specific.