This is possible on most operating systems with simple system calls, in Go you can use package syscall
to do system calls without any extra C code or cgo compiler.
Note that the online official documentation of the syscall
package shows only the linux interface. Other operating systems have slightly different interface, e.g. on Windows the syscall
package also contains a syscall.DLL
type, syscall.LoadDLL()
and syscall.MustLoadDLL()
functions, among others. To view these, run the godoc
tool locally, e.g.
godoc -http=:6060
This starts a webserver that hosts a web page similar to godoc.org
, navigate to http://localhost:6060/pkg/syscall/
. You can also see platform-specific doc online, for details, see How to access platform specific package documentation?
Here I present a complete, runnable Windows solution in pure Go. The complete example application is available on the Go Playground. It doesn't run on the playground, download it and run it locally (on Windows).
Let's define the type to describe hotkeys we want to use. This is not Windows-specific, just to make our code nicer. The Hotkey.String()
method provides a human-friendly display name of the hotkey such as "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)
}
The windows user32.dll
contains functions for global hotkey management. Let's load it:
user32 := syscall.MustLoadDLL("user32")
defer user32.Release()
It has a RegisterHotkey()
function for registering global hotkeys:
reghotkey := user32.MustFindProc("RegisterHotKey")
Using this, let's register some hotkeys, namely ALT+CTRL+O, ALT+SHIFT+M, ALT+CTRL+X (which will be used to exit from the app):
// 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)
}
}
We need a way to "listen" to events of pressing those hotkeys. For this, user32.dll
contains the PeekMessage()
function:
peekmsg := user32.MustFindProc("PeekMessageW")
PeekMessage()
stores the message into an MSG
struct, let's define it:
type MSG struct {
HWND uintptr
UINT uintptr
WPARAM int16
LPARAM int64
DWORD int32
POINT struct{ X, Y int64 }
}
Now here's our listening loop which listens and acts on global key presses (here just simply print the pressed hotkey to the console, and if CTRL+ALT+X is pressed, exit from the app):
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)
}
And we're done!
Starting the above application prints:
Registered Hotkey[Id: 1, Alt+Ctrl+O]
Registered Hotkey[Id: 2, Alt+Shift+M]
Registered Hotkey[Id: 3, Alt+Ctrl+X]
Now let's press some of the registered hotkeys (with any app being in focus, not necessarily our app), we'll see on the console:
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...
go
tag. – Pulpit