让我们说有人想在Go(golang)中创建一个跨平台(Mac,Linux,Windows)全球热键 - 你在操作系统的任何地方按热键组合,让我们说出打印的东西在终端。
目前,(2016年7月)我还没有找到任何图书馆,所以也许我们可以一起找到一条路。
当然,它会涉及为每个操作系统调用一些本机操作系统绑定,但是有关如何执行操作的信息很少。
从谷歌搜索判断应该使用addGlobalMonitorForEventsMatchingMask
编辑:无用的示例已删除
看起来嫌疑人是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
非常感谢任何帮助!
答案 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调用昂贵得多,可靠性也不高。
与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的潜伏者,但我实际上从未发布过任何内容,因此没有足够的内容声誉。