Внедрить глобальную горячую клавишу в golang?

Скажем, кто-то хочет создать глобальную горячую клавишу кросс-платформенной (Mac, Linux, Windows) в Go (golang) - вы нажимаете комбинацию горячих клавиш в любом месте ОС и можете сказать, что что-то напечатано в терминале.

В настоящее время (июль 2016 года) я не нашел библиотеки для этого, так что, возможно, мы сможем найти способ вместе.

Это потребует, конечно, вызова некоторых привязок к ОС для каждой ОС, но есть очень редкая информация о том, как это сделать.

Mac

Судя по Googling, следует использовать addGlobalMonitorForEventsMatchingMask

ИЗМЕНИТЬ: бесполезный пример удален

Linux

Похоже, что подозреваемый XGrabKey, хотя никакого примера кода в ближайшем будущем нет https://github.com/search?utf8=%E2%9C%93&q=language%3Ago+XGrabKey&type=Repositories&ref=searchresults

Окна

Кажется, что нам нужно использовать RegisterHotKey, но попытка найти какой-то пример кода приводит нигде: https://github.com/search?utf8=%E2%9C%93&q=language%3Ago+RegisterHotKey

Некоторый интересный кросс-платформенный проект для исследования (на Java) - https://github.com/tulskiy/jkeymaster

Любая помощь будет принята с благодарностью!

Ответ 1

Это возможно в большинстве операционных систем с простыми системными вызовами, в Go вы можете использовать пакет syscall для выполнения системных вызовов без каких-либо дополнительных C или компилятор cgo.

Обратите внимание, что официальная документация в Интернете пакета syscall показывает только интерфейс linux. Другие операционные системы имеют немного другой интерфейс, например. в Windows пакет syscall также содержит функции syscall.DLL, syscall.LoadDLL() и syscall.MustLoadDLL(). Чтобы просмотреть их, запустите инструмент godoc локально, например.

godoc -http=:6060

Запустив веб-сервер, на котором размещена веб-страница, похожая на godoc.org, перейдите к http://localhost:6060/pkg/syscall/.

Здесь я представляю полное, исполняемое решение Windows в чистом Go. Полное примерное приложение доступно на Go Playground. Он не запускается на игровой площадке, загружает его и запускает локально (в Windows).


Определите тип описания горячих клавиш, которые мы хотим использовать. Это не зависит от Windows, просто чтобы сделать наш код приятнее. Метод Hotkey.String() обеспечивает удобное для пользователя отображаемое имя горячей клавиши, например "Hotkey[Id: 1, Alt+Ctrl+O]".

const (
    ModAlt = 1 << iota
    ModCtrl
    ModShift
    ModWin
)

type Hotkey struct {
    Id        int // Unique id
    Modifiers int // Mask of modifiers
    KeyCode   int // Key code, e.g. 'A'
}

// String returns a human-friendly display name of the hotkey
// such as "Hotkey[Id: 1, Alt+Ctrl+O]"
func (h *Hotkey) String() string {
    mod := &bytes.Buffer{}
    if h.Modifiers&ModAlt != 0 {
        mod.WriteString("Alt+")
    }
    if h.Modifiers&ModCtrl != 0 {
        mod.WriteString("Ctrl+")
    }
    if h.Modifiers&ModShift != 0 {
        mod.WriteString("Shift+")
    }
    if h.Modifiers&ModWin != 0 {
        mod.WriteString("Win+")
    }
    return fmt.Sprintf("Hotkey[Id: %d, %s%c]", h.Id, mod, h.KeyCode)
}

Окна user32.dll содержат функции глобального управления горячими клавишами. Позвольте загрузить его:

user32 := syscall.MustLoadDLL("user32")
defer user32.Release()

У него есть функция RegisterHotkey() для регистрации глобальных горячих клавиш:

reghotkey := user32.MustFindProc("RegisterHotKey")

С помощью этого, зарегистрируйте некоторые горячие клавиши, а именно ALT+CTRL+O, ALT+SHIFT+M, ALT+CTRL+X (которые будут использоваться для выхода из приложения):

// Hotkeys to listen to:
keys := map[int16]*Hotkey{
    1: &Hotkey{1, ModAlt + ModCtrl, 'O'},  // ALT+CTRL+O
    2: &Hotkey{2, ModAlt + ModShift, 'M'}, // ALT+SHIFT+M
    3: &Hotkey{3, ModAlt + ModCtrl, 'X'},  // ALT+CTRL+X
}

// Register hotkeys:
for _, v := range keys {
    r1, _, err := reghotkey.Call(
        0, uintptr(v.Id), uintptr(v.Modifiers), uintptr(v.KeyCode))
    if r1 == 1 {
        fmt.Println("Registered", v)
    } else {
        fmt.Println("Failed to register", v, ", error:", err)
    }
}

Нам нужен способ "прослушать" события нажатия этих горячих клавиш. Для этого user32.dll содержит функцию PeekMessage():

peekmsg := user32.MustFindProc("PeekMessageW")

PeekMessage() сохраняет сообщение в MSG struct, пусть определит его:

type MSG struct {
    HWND   uintptr
    UINT   uintptr
    WPARAM int16
    LPARAM int64
    DWORD  int32
    POINT  struct{ X, Y int64 }
}

Теперь вот наш цикл прослушивания, который прослушивает и действует на глобальные нажатия клавиш (просто просто напечатайте нажатую горячую клавишу на консоли, и если CTRL+ALT+X нажата, выйдите из приложения):

for {
    var msg = &MSG{}
    peekmsg.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0, 1)

    // Registered id is in the WPARAM field:
    if id := msg.WPARAM; id != 0 {
        fmt.Println("Hotkey pressed:", keys[id])
        if id == 3 { // CTRL+ALT+X = Exit
            fmt.Println("CTRL+ALT+X pressed, goodbye...")
            return
        }
    }

    time.Sleep(time.Millisecond * 50)
}

И все готово!

Запуск вышеприведенного приложения печатает:

Registered Hotkey[Id: 1, Alt+Ctrl+O]
Registered Hotkey[Id: 2, Alt+Shift+M]
Registered Hotkey[Id: 3, Alt+Ctrl+X]

Теперь позвольте нажать некоторые из зарегистрированных горячих клавиш (любое приложение находится в фокусе, не обязательно наше приложение), мы увидим на консоли:

Hotkey pressed: Hotkey[Id: 1, Alt+Ctrl+O]
Hotkey pressed: Hotkey[Id: 1, Alt+Ctrl+O]
Hotkey pressed: Hotkey[Id: 2, Alt+Shift+M]
Hotkey pressed: Hotkey[Id: 3, Alt+Ctrl+X]
CTRL+ALT+X pressed, goodbye...

Ответ 2

Это можно сделать. Например, игровой движок Azul3d может сделать это https://github.com/azul3d/engine/tree/master/keyboard. Ваш фрагмент OSX не компилируется, потому что отсутствует файл AppDelegate.h. Вы можете либо написать его вручную, либо сначала скомпилировать проект с помощью XCode, который выполняет бухгалтерию для вас, а затем просмотрите AppDelegate.h и AppDelegate.m. В linux для записи потока нажатия клавиши вы можете просто прочитать файл /dev/input/event $ID, который делается здесь https://github.com/MarinX/keylogger. В окнах вам понадобятся некоторые системные вызовы, как в этом коде https://github.com/eiannone/keyboard. И на nix, и на выигрыш вам даже не нужно cgo, и вы можете быть счастливы с syscalls и чистым Go.