在golang中实现全局热键?

时间:2016-07-28 21:09:39

标签: go cross-platform hotkeys

让我们说有人想在Go(golang)中创建一个跨平台(Mac,Linux,Windows)全球热键 - 你在操作系统的任何地方按热键组合,让我们说出打印的东西在终端。

目前,(2016年7月)我还没有找到任何图书馆,所以也许我们可以一起找到一条路。

当然,它会涉及为每个操作系统调用一些本机操作系统绑定,但是有关如何执行操作的信息很少。

的Mac

从谷歌搜索判断应该使用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

非常感谢任何帮助!

3 个答案:

答案 0 :(得分:14)

这在大多数具有简单系统调用的操作系统上是可行的,在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/

在这里,我在pure Go中展示了一个完整的,可运行的Windows解决方案。完整的示例应用程序可在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)
}

windows 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)
    }
}

我们需要一种方法来聆听&#34;对那些按热键的事件。为此,user32.dll包含PeekMessage()函数:

peekmsg := user32.MustFindProc("PeekMessageW")

PeekMessage()将邮件存储到MSG结构中,让我们定义它:

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...

答案 1 :(得分: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。在Windows上,您需要一些系统调用,例如此代码https://github.com/eiannone/keyboard。在nix和win上你甚至不需要cgo,并且可以对系统调用和纯Go感到满意。

答案 2 :(得分:2)

icza的回答很棒,对我有很大帮助,但是我发现使用PeekMessageW API调用比GetMessageW API调用昂贵得多,可靠性也不高。

根据Microsoft documentation

与GetMessage不同,PeekMessage函数在返回消息之前不等待消息发布。

根据我对Windows内部构件的有限了解,这意味着PeekMessageW不断检查消息队列并返回“ 0”,直到找到事件为止,从而导致不必要的计算。

只需将代码更改为:

getmsg := user32.MustFindProc("GetMessageW")

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

为我修复了此问题。这意味着您还可以摆脱超时而不会明显降低性能:

    time.Sleep(time.Millisecond * 50)

我确实想知道可靠性问题是否源于我在goroutine中运行热键循环的事实。我对Go或Windows API的了解不足,但是希望这对某人有帮助!我也没有经历过这种更改引起的任何内存泄漏,尽管我无法按照Programming4life的建议验证原始代码是否存在内存泄漏。

注意:我会在他的原始帖子下发表评论并提出建议,但我长期以来一直是StackOverflow的潜伏者,但我实际上从未发布过任何内容,因此没有足够的内容声誉。