1

I'm trying to call SetWinEventHook as described here for C# but from nodejs.

I'm using ffi-napi to bind to the function. Here's my code so far:

const ffi = require("ffi-napi")

const user32 = ffi.Library("user32", {
    SetWinEventHook: ["int", ["int", "int", "pointer", "pointer", "int", "int", "int"]]
})

const pfnWinEventProc = ffi.Callback("void", ["pointer", "int", "pointer", "long", "long", "int", "int"],
    function (hWinEventHook, event, hwnd, idObject, idChild, idEventThread, dwmsEventTime) {
        console.log("Callback!")
        console.log(arguments)
    })

const EVENT_SYSTEM_FOREGROUND = 3
const WINEVENT_OUTOFCONTEXT = 0
const WINEVENT_SKPIOWNPROCESS = 2

user32.SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, null, pfnWinEventProc,
    0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKPIOWNPROCESS)

setInterval(function () {
    // keep the script alive
}, 500)

process.on("exit", function () {
    console.log("Exiting")
    pfnWinEventProc
})

The problem is simply my callback is not being called. It should be called whenever the focused window is changed.

I'm not getting any errors either so I'm quite lost as to what I'm doing wrong here.

The code is here as well if you want to check it out.

MadeOfAir
  • 2,933
  • 5
  • 31
  • 39
  • Error checking required. And the callback can only be made when you ask for messages, a message loop is required as [noted here](https://stackoverflow.com/questions/17737018/how-do-i-pump-window-messages-in-a-nodejs-addon). – Hans Passant Dec 14 '18 at 06:39
  • @HansPassant thanks for commenting. Can you please clarify? What kind of error checking? Also why should I use busy-wait loop while `setInterval` is keeping the script running without hogging the runtime? – MadeOfAir Dec 15 '18 at 03:55

1 Answers1

3

I finally got it working using a combination of the cluster module (if you don't want to block the event loop with the message loop) and peeking at the Windows part of the active-win package.

The full code is here.

const ffi = require("ffi-napi")
const cluster = require("cluster")
const ref = require("ref")
const wchar = require("ref-wchar")

if (cluster.isMaster) {
    console.log("Main code here...")
    cluster.fork()
} else {
    const msgType = ref.types.void
    const msgPtr = ref.refType(msgType)
    const EVENT_SYSTEM_FOREGROUND = 3
    const WINEVENT_OUTOFCONTEXT = 0
    const WINEVENT_SKPIOWNPROCESS = 2

    const user32 = ffi.Library("user32", {
        SetWinEventHook: ["int", ["int", "int", "pointer", "pointer", "int", "int", "int"]],
        GetWindowTextW: ["int", ["pointer", "pointer", "int"]],
        GetWindowTextLengthW: ["int", ["pointer"]],
        GetMessageA: ["bool", [msgPtr, "int", "uint", "uint"]]
    })

    function getMessage() {
        return user32.GetMessageA(ref.alloc(msgPtr), null, 0, 0)
    }

    const pfnWinEventProc = ffi.Callback("void", ["pointer", "int", "pointer", "long", "long", "int", "int"],
        function (hWinEventHook, event, hwnd, idObject, idChild, idEventThread, dwmsEventTime) {
            const windowTitleLength = user32.GetWindowTextLengthW(hwnd)
            const bufferSize = windowTitleLength * 2 + 4
            const titleBuffer = Buffer.alloc(bufferSize)
            user32.GetWindowTextW(hwnd, titleBuffer, bufferSize)
            const titleText = ref.reinterpretUntilZeros(titleBuffer, wchar.size)
            const finallyWindowTitle = wchar.toString(titleText)
            console.log(finallyWindowTitle)
        }
    )

    user32.SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, null, pfnWinEventProc,
        0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKPIOWNPROCESS)

    let res = getMessage()
    while(res != 0) {
        switch (res) {
            case -1:
                console.log("Invalid GetMessageA arguments or something!");
                break
            default:
                console.log("Got a message!")
        }
        res = getMessage()
    }
}
MadeOfAir
  • 2,933
  • 5
  • 31
  • 39
  • If all you need the message-loop for is to trigger already-registered keyboard/mouse hook listeners (or the like), another option is to create a timer in the regular/main NodeJS thread (I set interval to 1ms -- not that it reaches that frequency), and call `PeekMessageA` there. Not the same use-case, but I figured I would mention it. – Venryx Sep 08 '20 at 15:17