Skip to content

Commit 4bbd887

Browse files
committed
all: allow unregister hotkeys (windows)
Updates #5, #7
1 parent 30b843f commit 4bbd887

File tree

3 files changed

+53
-15
lines changed

3 files changed

+53
-15
lines changed

hotkey_windows.go

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,84 @@ package hotkey
1010

1111
import (
1212
"context"
13+
"errors"
14+
"runtime"
15+
"sync"
16+
"sync/atomic"
1317

1418
"golang.design/x/hotkey/internal/win"
1519
)
1620

21+
type platformHotkey struct {
22+
mu sync.Mutex
23+
hotkeyId uint64
24+
registered bool
25+
ctx context.Context
26+
cancel context.CancelFunc
27+
canceled chan struct{}
28+
}
29+
30+
var hotkeyId uint64 // atomic
31+
1732
// register registers a system hotkey. It returns an error if
1833
// the registration is failed. This could be that the hotkey is
1934
// conflict with other hotkeys.
2035
func (hk *Hotkey) register() error {
36+
hk.mu.Lock()
37+
if hk.registered {
38+
hk.mu.Unlock()
39+
return errors.New("hotkey already registered.")
40+
}
2141
mod := uint8(0)
2242
for _, m := range hk.mods {
2343
mod = mod | uint8(m)
2444
}
25-
ok, err := win.RegisterHotKey(0, 1, uintptr(mod), uintptr(hk.key))
45+
46+
hk.hotkeyId = atomic.AddUint64(&hotkeyId, 1)
47+
ok, err := win.RegisterHotKey(0, uintptr(hk.hotkeyId), uintptr(mod), uintptr(hk.key))
2648
if !ok {
2749
return err
2850
}
51+
hk.registered = true
52+
hk.ctx, hk.cancel = context.WithCancel(context.Background())
53+
hk.canceled = make(chan struct{})
54+
hk.mu.Unlock()
55+
56+
go hk.handle()
2957
return nil
3058
}
3159

3260
// unregister deregisteres a system hotkey.
33-
func (hk *Hotkey) unregister() {
34-
// FIXME: unregister hotkey
61+
func (hk *Hotkey) unregister() error {
62+
hk.mu.Lock()
63+
defer hk.mu.Unlock()
64+
if !hk.registered {
65+
return errors.New("hotkey is not registered")
66+
}
67+
hk.cancel()
68+
<-hk.canceled
69+
hk.registered = false
70+
return nil
3571
}
3672

3773
// msgHotkey represents hotkey message
3874
const msgHotkey uint32 = 0x0312
3975

4076
// handle handles the hotkey event loop.
41-
func (hk *Hotkey) handle(ctx context.Context) {
77+
func (hk *Hotkey) handle() {
78+
runtime.LockOSThread()
79+
defer runtime.UnlockOSThread()
80+
4281
msg := win.MSG{}
4382
// KNOWN ISSUE: This message loop need to press an additional
4483
// hotkey to eventually return if ctx.Done() is ready.
4584
for win.GetMessage(&msg, 0, 0, 0) {
4685
switch msg.Message {
4786
case msgHotkey:
4887
select {
49-
case <-ctx.Done():
88+
case <-hk.ctx.Done():
89+
win.UnregisterHotKey(0, uintptr(hk.hotkeyId))
90+
close(hk.canceled)
5091
return
5192
default:
5293
hk.in <- Event{}

hotkey_windows_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,16 @@ func TestHotkey(t *testing.T) {
2626
ctx, cancel := context.WithTimeout(context.Background(), tt)
2727
defer cancel()
2828

29-
hk, err := hotkey.Register([]hotkey.Modifier{
30-
hotkey.ModCtrl, hotkey.ModShift}, hotkey.KeyS)
31-
if err != nil {
29+
hk := hotkey.New([]hotkey.Modifier{hotkey.ModCtrl, hotkey.ModShift}, hotkey.KeyS)
30+
if err := hk.Register(); err != nil {
3231
t.Errorf("failed to register hotkey: %v", err)
3332
return
3433
}
35-
36-
trigger := hk.Listen(ctx)
3734
for {
3835
select {
3936
case <-ctx.Done():
4037
return
41-
case <-trigger:
38+
case <-hk.Listen():
4239
fmt.Println("triggered")
4340
}
4441
}

internal/win/hotkey.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,18 @@ var (
2424

2525
// RegisterHotKey defines a system-wide hot key.
2626
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey
27-
func RegisterHotKey(hwnd uintptr, id int, mod uintptr, k uintptr) (bool, error) {
27+
func RegisterHotKey(hwnd, id uintptr, mod uintptr, k uintptr) (bool, error) {
2828
ret, _, err := registerHotkey.Call(
29-
hwnd, uintptr(id), mod, k,
29+
hwnd, id, mod, k,
3030
)
3131
return ret != 0, err
3232
}
3333

3434
// UnregisterHotKey frees a hot key previously registered by the calling
3535
// thread.
3636
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unregisterhotkey
37-
func UnregisterHotKey(hwnd uintptr, id int) (bool, error) {
38-
ret, _, err := unregisterHotkey.Call(hwnd, uintptr(id))
37+
func UnregisterHotKey(hwnd, id uintptr) (bool, error) {
38+
ret, _, err := unregisterHotkey.Call(hwnd, id)
3939
return ret != 0, err
4040
}
4141

0 commit comments

Comments
 (0)