-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
hotkey.go
181 lines (164 loc) · 4.93 KB
/
hotkey.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
// Package hotkey provides the basic facility to register a system-level
// global hotkey shortcut so that an application can be notified if a user
// triggers the desired hotkey. A hotkey must be a combination of modifiers
// and a single key.
//
// Note platform specific details:
//
// - On macOS, due to the OS restriction (other platforms does not have
// this restriction), hotkey events must be handled on the "main thread".
// Therefore, in order to use this package properly, one must start an
// OS main event loop on the main thread, For self-contained applications,
// using [mainthread] package.
// is possible. It is uncessary or applications based on other GUI frameworks,
// such as fyne, ebiten, or Gio. See the "[examples]" for more examples.
//
// - On Linux (X11), when AutoRepeat is enabled in the X server, the
// Keyup is triggered automatically and continuously as Keydown continues.
//
// - On Linux (X11), some keys may be mapped to multiple Mod keys. To
// correctly register the key combination, one must use the correct
// underlying keycode combination. For example, a regular Ctrl+Alt+S
// might be registered as: Ctrl+Mod2+Mod4+S.
//
// - If this package did not include a desired key, one can always provide
// the keycode to the API. For example, if a key code is 0x15, then the
// corresponding key is `hotkey.Key(0x15)`.
//
// THe following is a minimum example:
//
// package main
//
// import (
// "log"
//
// "golang.design/x/hotkey"
// "golang.design/x/hotkey/mainthread"
// )
//
// func main() { mainthread.Init(fn) } // Not necessary when use in Fyne, Ebiten or Gio.
// func fn() {
// hk := hotkey.New([]hotkey.Modifier{hotkey.ModCtrl, hotkey.ModShift}, hotkey.KeyS)
// err := hk.Register()
// if err != nil {
// log.Fatalf("hotkey: failed to register hotkey: %v", err)
// }
//
// log.Printf("hotkey: %v is registered\n", hk)
// <-hk.Keydown()
// log.Printf("hotkey: %v is down\n", hk)
// <-hk.Keyup()
// log.Printf("hotkey: %v is up\n", hk)
// hk.Unregister()
// log.Printf("hotkey: %v is unregistered\n", hk)
// }
//
// [mainthread]: https://pkg.go.dev/golang.design/x/hotkey/mainthread
// [examples]: https://github.com/golang-design/hotkey/tree/main/examples
package hotkey
import (
"fmt"
"runtime"
)
// Event represents a hotkey event
type Event struct{}
// Hotkey is a combination of modifiers and key to trigger an event
type Hotkey struct {
platformHotkey
mods []Modifier
key Key
keydownIn chan<- Event
keydownOut <-chan Event
keyupIn chan<- Event
keyupOut <-chan Event
}
// New creates a new hotkey for the given modifiers and keycode.
func New(mods []Modifier, key Key) *Hotkey {
keydownIn, keydownOut := newEventChan()
keyupIn, keyupOut := newEventChan()
hk := &Hotkey{
mods: mods,
key: key,
keydownIn: keydownIn,
keydownOut: keydownOut,
keyupIn: keyupIn,
keyupOut: keyupOut,
}
// Make sure the hotkey is unregistered when the created
// hotkey is garbage collected.
runtime.SetFinalizer(hk, func(x interface{}) {
hk := x.(*Hotkey)
hk.unregister()
close(hk.keydownIn)
close(hk.keyupIn)
})
return hk
}
// Register registers a combination of hotkeys. If the hotkey has
// registered. This function will invalidates the old registration
// and overwrites its callback.
func (hk *Hotkey) Register() error { return hk.register() }
// Keydown returns a channel that receives a signal when the hotkey is triggered.
func (hk *Hotkey) Keydown() <-chan Event { return hk.keydownOut }
// Keyup returns a channel that receives a signal when the hotkey is released.
func (hk *Hotkey) Keyup() <-chan Event { return hk.keyupOut }
// Unregister unregisters the hotkey.
func (hk *Hotkey) Unregister() error {
err := hk.unregister()
if err != nil {
return err
}
// Reset a new event channel.
close(hk.keydownIn)
close(hk.keyupIn)
hk.keydownIn, hk.keydownOut = newEventChan()
hk.keyupIn, hk.keyupOut = newEventChan()
return nil
}
// String returns a string representation of the hotkey.
func (hk *Hotkey) String() string {
s := fmt.Sprintf("%v", hk.key)
for _, mod := range hk.mods {
s += fmt.Sprintf("+%v", mod)
}
return s
}
// newEventChan returns a sender and a receiver of a buffered channel
// with infinite capacity.
func newEventChan() (chan<- Event, <-chan Event) {
in, out := make(chan Event), make(chan Event)
go func() {
var q []Event
for {
e, ok := <-in
if !ok {
close(out)
return
}
q = append(q, e)
for len(q) > 0 {
select {
case out <- q[0]:
q[0] = Event{}
q = q[1:]
case e, ok := <-in:
if ok {
q = append(q, e)
break
}
for _, e := range q {
out <- e
}
close(out)
return
}
}
}
}()
return in, out
}