Skip to content

Commit 1d76324

Browse files
committed
all: initial implementation
1 parent 0fdd634 commit 1d76324

19 files changed

+1253
-3
lines changed

.github/FUNDING.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# These are supported funding model platforms
2+
3+
github: [changkun] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4+
patreon: # Replace with a single Patreon username
5+
open_collective: # Replace with a single Open Collective username
6+
ko_fi: # Replace with a single Ko-fi username
7+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9+
liberapay: # Replace with a single Liberapay username
10+
issuehunt: # Replace with a single IssueHunt username
11+
otechie: # Replace with a single Otechie username
12+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

.github/workflows/hotkey.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2021 The golang.design Initiative Authors.
2+
# All rights reserved. Use of this source code is governed
3+
# by a MIT license that can be found in the LICENSE file.
4+
#
5+
# Written by Changkun Ou <changkun.de>
6+
7+
name: hotkey
8+
9+
on:
10+
push:
11+
branches: [ main ]
12+
pull_request:
13+
branches: [ main ]
14+
15+
jobs:
16+
platform_test:
17+
18+
runs-on: ${{ matrix.os }}
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
os: [ubuntu-latest, macos-latest, windows-latest]
23+
24+
steps:
25+
26+
- name: install xvfb libx11-dev
27+
run: |
28+
sudo apt update
29+
sudo apt install -y xvfb libx11-dev
30+
if: ${{ runner.os == 'Linux' }}
31+
32+
- uses: actions/checkout@v2
33+
- uses: actions/setup-go@v2
34+
with:
35+
stable: 'false'
36+
go-version: '1.16.0'
37+
38+
- name: TestLinux
39+
run: |
40+
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
41+
export DISPLAY=:99.0
42+
sleep 5s
43+
go test -v -covermode=atomic ./...
44+
if: ${{ runner.os == 'Linux' }}
45+
46+
- name: TestOthers
47+
run: |
48+
go test -v -covermode=atomic ./...

README.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,70 @@
1-
# hotkey
2-
cross platform hotkey package
1+
# hotkey [![PkgGoDev](https://pkg.go.dev/badge/golang.design/x/hotkey)](https://pkg.go.dev/golang.design/x/hotkey) ![](https://changkun.de/urlstat?mode=github&repo=golang-design/hotkey) ![hotkey](https://github.com/golang-design/hotkey/workflows/hotkey/badge.svg?branch=main)
2+
3+
cross platform hotkey package in Go
4+
5+
```go
6+
import "golang.design/x/hotkey"
7+
```
8+
9+
## Features
10+
11+
- Cross platform supports: macOS, Linux (X11), and Windows
12+
- Global hotkey registration without focus on a window
13+
14+
## API Usage
15+
16+
Package `hotkey` provides the basic facility to register a system-level
17+
hotkey so that the application can be notified if a user triggers the
18+
desired hotkey. By definition, a hotkey is a combination of modifiers
19+
and a single key, and thus register a hotkey that contains multiple
20+
keys is not supported at the moment. Furthermore, because of OS
21+
restriction, hotkey events must be handled on the main thread.
22+
23+
Therefore, in order to use this package properly, here is a complete
24+
example that corporates the [mainthread](https://golang.design/s/mainthread)
25+
package:
26+
27+
```go
28+
package main
29+
30+
import (
31+
"context"
32+
33+
"golang.design/x/hotkey"
34+
"golang.design/x/mainthread"
35+
)
36+
37+
// initialize mainthread facility so that hotkey can be
38+
// properly registered to the system and handled by the
39+
// application.
40+
func main() { mainthread.Init(fn) }
41+
func fn() { // Use fn as the actual main function.
42+
var (
43+
mods = []hotkey.Modifier{hotkey.ModCtrl}
44+
k = hotkey.KeyS
45+
)
46+
47+
// Register a desired hotkey.
48+
hk, err := hotkey.Register(mods, k)
49+
if err != nil {
50+
panic("hotkey registration failed")
51+
}
52+
53+
// Start listen hotkey event whenever you feel it is ready.
54+
triggered := hk.Listen(context.Background())
55+
for range triggered {
56+
println("hotkey ctrl+s is triggered")
57+
}
58+
}
59+
```
60+
61+
## Who is using this package?
62+
63+
The main purpose of building this package is to support the
64+
[midgard](https://changkun.de/s/midgard) project.
65+
66+
To know more projects, check our [wiki](https://github.com/golang-design/clipboard/wiki) page.
67+
68+
## License
69+
70+
MIT | &copy; 2021 The golang.design Initiative Authors, written by [Changkun Ou](https://changkun.de).

example/main.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2021 The golang.design Initiative Authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a MIT license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"context"
9+
10+
"golang.design/x/hotkey"
11+
"golang.design/x/mainthread"
12+
)
13+
14+
func main() { mainthread.Init(fn) }
15+
func fn() {
16+
var (
17+
mods = []hotkey.Modifier{hotkey.ModCtrl}
18+
k = hotkey.KeyS
19+
)
20+
21+
hk, err := hotkey.Register(mods, k)
22+
if err != nil {
23+
panic("hotkey registration failed")
24+
}
25+
26+
triggered := hk.Listen(context.Background())
27+
for range triggered {
28+
println("hotkey ctrl+s is triggered")
29+
}
30+
}

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
module golang.design/x/hotkey
22

3-
go 1.16
3+
go 1.16
4+
5+
require (
6+
golang.design/x/mainthread v0.2.1
7+
golang.org/x/sys v0.0.0-20210122093101-04d7465088b8 // indirect
8+
)

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
golang.design/x/mainthread v0.2.1 h1:IUGVW1acDfKoQtFeeS/RD/YYiKK8jxwkJXIQuKuL+ig=
2+
golang.design/x/mainthread v0.2.1/go.mod h1:vYX7cF2b3pTJMGM/hc13NmN6kblKnf4/IyvHeu259L0=
3+
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
4+
golang.org/x/sys v0.0.0-20210122093101-04d7465088b8 h1:de2yTH1xuxjmGB7i6Z5o2z3RCHVa0XlpSZzjd8Fe6bE=
5+
golang.org/x/sys v0.0.0-20210122093101-04d7465088b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

hotkey.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2021 The golang.design Initiative Authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a MIT license that can be found in the LICENSE file.
4+
//
5+
// Written by Changkun Ou <changkun.de>
6+
7+
// Package hotkey provides the basic facility to register a system-level
8+
// hotkey so that the application can be notified if a user triggers the
9+
// desired hotkey. By definition, a hotkey is a combination of modifiers
10+
// and a single key, and thus register a hotkey that contains multiple
11+
// keys is not supported at the moment. Furthermore, because of OS
12+
// restriction, hotkey events must be handled on the main thread.
13+
//
14+
// Therefore, in order to use this package properly, here is a complete
15+
// example that corporates the golang.design/x/mainthread package:
16+
//
17+
// package main
18+
//
19+
// import (
20+
// "context"
21+
//
22+
// "golang.design/x/hotkey"
23+
// "golang.design/x/mainthread"
24+
// )
25+
//
26+
// // initialize mainthread facility so that hotkey can be
27+
// // properly registered to the system and handled by the
28+
// // application.
29+
// func main() { mainthread.Init(fn) }
30+
// func fn() { // Use fn as the actual main function.
31+
// var (
32+
// mods = []hotkey.Modifier{hotkey.ModCtrl}
33+
// k = hotkey.KeyS
34+
// )
35+
//
36+
// // Register a desired hotkey.
37+
// hk, err := hotkey.Register(mods, k)
38+
// if err != nil {
39+
// panic("hotkey registration failed")
40+
// }
41+
//
42+
// // Start listen hotkey event whenever you feel it is ready.
43+
// triggered := hk.Listen(context.Background())
44+
// for range triggered {
45+
// println("hotkey ctrl+s is triggered")
46+
// }
47+
// }
48+
package hotkey
49+
50+
import (
51+
"context"
52+
"runtime"
53+
54+
"golang.design/x/mainthread"
55+
)
56+
57+
// Event represents a hotkey event
58+
type Event struct{}
59+
60+
// Hotkey is a combination of modifiers and key to trigger an event
61+
type Hotkey struct {
62+
mods []Modifier
63+
key Key
64+
65+
in chan<- Event
66+
out <-chan Event
67+
}
68+
69+
// Register registers a combination of hotkeys. If the hotkey has
70+
// registered. This function will invalidates the old registration
71+
// and overwrites its callback.
72+
func Register(mods []Modifier, key Key) (*Hotkey, error) {
73+
in, out := newEventChan()
74+
hk := &Hotkey{mods, key, in, out}
75+
76+
var err error
77+
mainthread.Call(func() { err = hk.register() })
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
runtime.SetFinalizer(hk, func(hk *Hotkey) {
83+
hk.unregister()
84+
})
85+
86+
return hk, nil
87+
}
88+
89+
// Listen handles a hotkey event and triggers a call to fn.
90+
// The hotkey listen hook terminates when the context is canceled.
91+
func (hk *Hotkey) Listen(ctx context.Context) <-chan Event {
92+
mainthread.Go(func() { hk.handle(ctx) })
93+
return hk.out
94+
}
95+
96+
// newEventChan returns a sender and a receiver of a buffered channel
97+
// with infinite capacity.
98+
func newEventChan() (chan<- Event, <-chan Event) {
99+
in, out := make(chan Event), make(chan Event)
100+
101+
go func() {
102+
var q []Event
103+
104+
for {
105+
e, ok := <-in
106+
if !ok {
107+
close(out)
108+
return
109+
}
110+
q = append(q, e)
111+
for len(q) > 0 {
112+
select {
113+
case out <- q[0]:
114+
q = q[1:]
115+
case e, ok := <-in:
116+
if ok {
117+
q = append(q, e)
118+
break
119+
}
120+
for _, e := range q {
121+
out <- e
122+
}
123+
close(out)
124+
return
125+
}
126+
}
127+
}
128+
}()
129+
return in, out
130+
}

0 commit comments

Comments
 (0)