在golang中实现全局热键?

Sla*_*a V 17 cross-platform hotkeys go

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

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

当然,它将涉及为每个操作系统调用一些本机操作系统绑定,但是关于如何执行操作的信息非常稀少.

苹果电脑

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

任何帮助将不胜感激!

icz*_*cza 14

这在大多数具有简单系统调用的操作系统上是可行的,在Go中,您可以使用包syscall来进行系统调用,而无需任何额外的C代码或cgo编译器.

请注意,该syscall软件包的在线官方文档仅显示了linux接口.其他操作系统具有略微不同的界面,例如在Windows上,syscall包还包含syscall.DLL类型syscall.LoadDLL()和syscall.MustLoadDLL()功能等.要查看这些,请在godoc本地运行该工具,例如

godoc -http=:6060
Run Code Online (Sandbox Code Playgroud)

这将启动一个Web服务器,它托管一个类似于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)
}
Run Code Online (Sandbox Code Playgroud)

窗口user32.dll包含全局热键管理功能.我们加载吧:

user32 := syscall.MustLoadDLL("user32")
defer user32.Release()
Run Code Online (Sandbox Code Playgroud)

它具有RegisterHotkey()注册全局热键的功能:

reghotkey := user32.MustFindProc("RegisterHotKey")
Run Code Online (Sandbox Code Playgroud)

利用这一点,让我们来注册一些热键,即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)
    }
}
Run Code Online (Sandbox Code Playgroud)

我们需要一种方法来"倾听"按下这些热键的事件.为此,user32.dll包含PeekMessage()功能:

peekmsg := user32.MustFindProc("PeekMessageW")
Run Code Online (Sandbox Code Playgroud)

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

type MSG struct {
    HWND   uintptr
    UINT   uintptr
    WPARAM int16
    LPARAM int64
    DWORD  int32
    POINT  struct{ X, Y int64 }
}
Run Code Online (Sandbox Code Playgroud)

现在这里是我们的监听循环,它监听并作用于全局按键(这里只是简单地将按下的热键打印到控制台,如果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)
}
Run Code Online (Sandbox Code Playgroud)

我们完成了!

启动上面的应用程序打印:

Registered Hotkey[Id: 1, Alt+Ctrl+O]
Registered Hotkey[Id: 2, Alt+Shift+M]
Registered Hotkey[Id: 3, Alt+Ctrl+X]
Run Code Online (Sandbox Code Playgroud)

现在让我们按下一些注册的热键(任何应用程序都在焦点,不一定是我们的应用程序),我们将在控制台上看到:

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...
Run Code Online (Sandbox Code Playgroud)


小智 5

icza 的答案很棒,对我有很大帮助,但我发现使用 PeekMessageW API 调用比 GetMessageW API 调用要昂贵得多,而且可靠性较低。

根据微软文档:

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

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

只需将代码更改为:

getmsg := user32.MustFindProc("GetMessageW")

for {
    var msg = &MSG{}
    getmsg.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
Run Code Online (Sandbox Code Playgroud)

为我解决了这个问题。这意味着您还可以摆脱超时,而不会显着影响性能:

time.Sleep(time.Millisecond * 50)
Run Code Online (Sandbox Code Playgroud)

我确实想知道可靠性问题是否来自于我在 goroutine 中运行热键循环这一事实。我对 Go 或 Windows API 的经验不足,无法了解,但希望这对某人有帮助!我也没有遇到任何类型的内存泄漏与此更改,尽管我无法验证原始代码是否存在内存泄漏,如Programming4life建议的那样。

  • 很好的答案,这不仅可以防止忙等待,还可以消除轮询滞后! (2认同)

Uve*_*tel 4

可以办到。例如,游戏引擎 Azul3d 可以做到这一点https://github.com/azul3d/engine/tree/master/keyboard。您的 OSX 代码段无法编译,因为缺少 AppDelegate.h 文件。您可以手动编写它,也可以首先使用 XCode 编译项目,XCode 会为您记账,然后查看 AppDelegate.h 和 AppDelegate.m。在 Linux 上,要记录击键流,您只需读取 /dev/input/event$ID 文件即可,该文件在此处完成https://github.com/MarinX/keylogger。在 Windows 上,您将需要一些系统调用,如以下代码中的https://github.com/eiannanone/keyboard。在 nix 和 win 上,您甚至不需要 cgo,并且可以对系统调用和纯 Go 感到满意。