├── win ├── consts.go └── win.go ├── debuglog.go ├── server.go ├── example ├── minimal │ └── minimal.go ├── funckeys │ └── funckeys.go ├── once │ └── once.go └── once2 │ └── once2.go ├── example_test.go ├── LICENSE ├── README.md ├── mock_test.go ├── hotkey.go ├── hotkey_test.go ├── serverImpl.go └── virtual_key.go /win/consts.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package hotkey_win 7 | 8 | const ( 9 | MOD_NONE = 0 10 | MOD_ALT = 1 11 | MOD_CONTROL = 2 12 | MOD_SHIFT = 4 13 | MOD_WIN = 8 14 | ) 15 | -------------------------------------------------------------------------------- /debuglog.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package hotkey 7 | 8 | import "log" 9 | 10 | type debugT bool 11 | 12 | func (d debugT) Log(fmt string, args ...interface{}) { 13 | if d { 14 | log.Printf(fmt, args...) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package hotkey 7 | 8 | type server interface { 9 | register(fsModifiers, vk uint32, handle func()) (Id, error) 10 | unregister(id int32) 11 | stop() 12 | isStop() bool 13 | 14 | // For debugging 15 | useDebugLog() 16 | } 17 | 18 | // For test 19 | var newServer func() server 20 | -------------------------------------------------------------------------------- /example/minimal/minimal.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/MakeNowJust/hotkey" 12 | ) 13 | 14 | func main() { 15 | hkey := hotkey.New() 16 | 17 | quit := make(chan bool) 18 | 19 | hkey.Register(hotkey.Ctrl, 'Q', func() { 20 | fmt.Println("Quit") 21 | quit <- true 22 | }) 23 | 24 | fmt.Println("Start hotkey's loop") 25 | fmt.Println("Push Ctrl-Q to escape and quit") 26 | <-quit 27 | } 28 | -------------------------------------------------------------------------------- /example/funckeys/funckeys.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/MakeNowJust/hotkey" 12 | ) 13 | 14 | func main() { 15 | hkey := hotkey.New() 16 | 17 | for i := uint32(0); i < 12; i++ { 18 | hkey.Register(hotkey.Alt+hotkey.Shift, hotkey.F1+i, func(i uint32) func() { 19 | return func() { 20 | fmt.Printf("Push Alt-Shift-F%d\n", i) 21 | } 22 | }(i+1)) 23 | } 24 | 25 | quit := make(chan bool) 26 | hkey.Register(hotkey.Ctrl, 'Q', func() { 27 | quit <- true 28 | }) 29 | 30 | fmt.Println(` 31 | Start hotkey's loop. 32 | 33 | Alt-Shift-F1..F12: Print key name. 34 | Ctrl-Q: Quit`[1:]) 35 | 36 | <-quit 37 | } 38 | -------------------------------------------------------------------------------- /example/once/once.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/MakeNowJust/hotkey" 12 | ) 13 | 14 | var hkey = hotkey.New() 15 | 16 | func registerOnce(mods hotkey.Modifier, vk uint32, handle func()) { 17 | var id hotkey.Id 18 | id, _ = hkey.Register(mods, vk, func() { 19 | handle() 20 | hkey.Unregister(id) 21 | }) 22 | } 23 | 24 | func main() { 25 | quit := make(chan bool) 26 | 27 | count := 0 28 | for _, vk := range "QUIT" { 29 | registerOnce(hotkey.Ctrl, uint32(vk), func(vk rune) func() { 30 | return func() { 31 | fmt.Printf("Push Ctrl-%c\n", vk) 32 | if count += 1; count == 4 { 33 | quit <- true 34 | } 35 | } 36 | }(vk)) 37 | } 38 | 39 | fmt.Println(` 40 | Start hotkey's loop. 41 | Push Ctrl-Q, U, I and T to quit. 42 | `[1:]) 43 | 44 | <-quit 45 | fmt.Println("QUIT!") 46 | } 47 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package hotkey_test 7 | 8 | import ( 9 | "fmt" 10 | "github.com/MakeNowJust/hotkey" 11 | ) 12 | 13 | func ExampleRegister() { 14 | hkey := hotkey.New() 15 | 16 | // Register Ctrl-A 17 | hkey.Register(hotkey.Ctrl, 'A', func() { 18 | // Here is a callback of HotKey Ctrl-A 19 | fmt.Println("Ctrl-A") 20 | }) 21 | 22 | // Register Ctrl-Alt-B 23 | hkey.Register(hotkey.Ctrl+hotkey.Alt, 'B', func() { 24 | fmt.Println("Ctrl-Alt-B") 25 | }) 26 | 27 | // Register Shift-Win-F1 28 | hkey.Register(hotkey.Shift+hotkey.Win, hotkey.F1, func() { 29 | fmt.Println("Shift-Win-F1") 30 | }) 31 | } 32 | 33 | func ExampleUnregister() { 34 | hkey := hotkey.New() 35 | 36 | // Register Ctrl-A. This callback will call only once. 37 | var id hotkey.Id 38 | id, _ = hkey.Register(hotkey.Ctrl, 'A', func() { 39 | fmt.Println("Ctrl-A") 40 | hkey.Unregister(id) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /example/once2/once2.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/MakeNowJust/hotkey" 12 | ) 13 | 14 | var hkey = hotkey.New() 15 | 16 | func registerOnce(mods hotkey.Modifier, vk uint32, handle func()) { 17 | var id hotkey.Id 18 | id, _ = hkey.Register(mods, vk, func() { 19 | handle() 20 | hkey.Unregister(id) 21 | }) 22 | } 23 | 24 | func registerLoop(str []rune, idx int, finish chan bool) { 25 | if idx < len(str) { 26 | registerOnce(hotkey.Ctrl, uint32(str[idx]), func() { 27 | fmt.Printf("Push Ctrl-%c\n", str[idx]) 28 | registerLoop(str, idx+1, finish) 29 | }) 30 | } else { 31 | finish <- true 32 | } 33 | } 34 | 35 | func main() { 36 | quit := make(chan bool) 37 | 38 | registerLoop([]rune("QUIT"), 0, quit) 39 | 40 | fmt.Println(` 41 | Start hotkey's loop. 42 | Push Ctrl-Q, U, I and T to quit. 43 | `[1:]) 44 | 45 | <-quit 46 | fmt.Println("QUIT!") 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hotkey [![Go Reference](https://pkg.go.dev/badge/github.com/MakeNowJust/hotkey.svg)](https://pkg.go.dev/github.com/MakeNowJust/hotkey) 2 | 3 | ## About 4 | 5 | This library provides HotKey for Go Language on Windows (including win32api wrapper, such as `RegisterHotKey`, `UnregisterHotKey`). 6 | 7 | ## Get Started 8 | 9 | ```console 10 | $ go get github.com/MakeNowJust/hotkey 11 | ``` 12 | 13 | ## Import 14 | 15 | ```go 16 | import "github.com/MakeNowJust/hotkey" 17 | ``` 18 | 19 | ## Usage 20 | 21 | The below is a minimal example. 22 | 23 | ```go 24 | package main 25 | 26 | import ( 27 | "fmt" 28 | 29 | "github.com/MakeNowJust/hotkey" 30 | ) 31 | 32 | func main() { 33 | hkey := hotkey.New() 34 | 35 | quit := make(chan bool) 36 | 37 | hkey.Register(hotkey.Ctrl, 'Q', func() { 38 | fmt.Println("Quit") 39 | quit <- true 40 | }) 41 | 42 | fmt.Println("Start hotkey's loop") 43 | fmt.Println("Push Ctrl-Q to escape and quit") 44 | <-quit 45 | } 46 | ``` 47 | 48 | Let's see the [`example/`](example/) for more examples. 49 | 50 | ## License 51 | 52 | This software is released under the MIT License. 53 | 54 | 55 | Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 56 | -------------------------------------------------------------------------------- /mock_test.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package hotkey 7 | 8 | var ( 9 | MockRegister func(fsModifers, vk uint32, handle func()) (Id, error) 10 | MockUnregister func(id int32) 11 | MockStop func() 12 | MockIsStop func() bool 13 | ) 14 | 15 | func init() { 16 | newServer = func() server { 17 | svr := mockServer{MockRegister, MockUnregister, MockStop, MockIsStop} 18 | MockRegister = nil 19 | MockUnregister = nil 20 | MockStop = nil 21 | MockIsStop = nil 22 | return &svr 23 | } 24 | } 25 | 26 | type mockServer struct { 27 | mockRegister func(fsModifiers, vk uint32, handle func()) (Id, error) 28 | mockUnregister func(id int32) 29 | mockStop func() 30 | mockIsStop func() bool 31 | } 32 | 33 | func (mock *mockServer) register(fsModifiers, vk uint32, handle func()) (id Id, err error) { 34 | if mock.mockRegister != nil { 35 | id, err = mock.mockRegister(fsModifiers, vk, handle) 36 | } 37 | return 38 | } 39 | 40 | func (mock *mockServer) unregister(id int32) { 41 | if mock.mockRegister != nil { 42 | mock.mockUnregister(id) 43 | } 44 | } 45 | 46 | func (mock *mockServer) stop() { 47 | if mock.mockStop != nil { 48 | mock.mockStop() 49 | } 50 | } 51 | 52 | func (mock *mockServer) isStop() bool { 53 | if mock.mockIsStop != nil { 54 | return mock.mockIsStop() 55 | } 56 | return false 57 | } 58 | 59 | func (mock *mockServer) useDebugLog() { 60 | // nothing 61 | } 62 | -------------------------------------------------------------------------------- /hotkey.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | // Package hotkey provides HotKey for Go Language. 7 | package hotkey 8 | 9 | import "github.com/MakeNowJust/hotkey/win" 10 | 11 | // A hotkey.Id is a identity number of registered hotkey. 12 | type Id int32 13 | 14 | // A hotkey.Modifier specifies the keyboard modifier for hotkey.Register. 15 | type Modifier uint32 16 | 17 | // These are all members of hotkey.Modifier. 18 | const ( 19 | None Modifier = hotkey_win.MOD_NONE 20 | Alt Modifier = hotkey_win.MOD_ALT 21 | Ctrl Modifier = hotkey_win.MOD_CONTROL 22 | Shift Modifier = hotkey_win.MOD_SHIFT 23 | Win Modifier = hotkey_win.MOD_WIN 24 | ) 25 | 26 | // A hotkey's manager. 27 | type Manager struct { 28 | svr server 29 | } 30 | 31 | // Create hotkey's manager and Start hotkey's loop. It is non-blocking. 32 | func New() (man *Manager) { 33 | man = new(Manager) 34 | man.svr = newServer() 35 | 36 | return 37 | } 38 | 39 | // Register a hotkey with modifiers and vk on man. 40 | // 41 | // mods are hotkey's modifiers such as hotkey.Alt, hotkey.Ctrl+hotkey.Shift. 42 | // 43 | // vk is a hotkey's virtual key code. See also 44 | // http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 45 | func (man *Manager) Register(mods Modifier, vk uint32, handle func()) (id Id, err error) { 46 | id, err = man.svr.register(uint32(mods), vk, handle) 47 | return 48 | } 49 | 50 | // Unregister a hotkey from id. 51 | func (man *Manager) Unregister(id Id) { 52 | man.svr.unregister(int32(id)) 53 | } 54 | 55 | // Stop hotkey's loop. 56 | func (man *Manager) Stop() { 57 | man.svr.stop() 58 | } 59 | 60 | // Check if hotkey's loop is stopping. 61 | func (man *Manager) IsStop() bool { 62 | return man.svr.isStop() 63 | } 64 | 65 | // For debugging. 66 | func (man *Manager) UseDebugLog() *Manager { 67 | man.svr.useDebugLog() 68 | return man 69 | } 70 | -------------------------------------------------------------------------------- /win/win.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | //go:build windows 7 | // +build windows 8 | 9 | // Package hotkey_win is win32api wrapper for hotkey. 10 | package hotkey_win 11 | 12 | import ( 13 | "github.com/lxn/win" 14 | "golang.org/x/sys/windows" 15 | ) 16 | 17 | var ( 18 | registerHotKey *windows.LazyProc 19 | unregisterHotKey *windows.LazyProc 20 | postThreadMessage *windows.LazyProc 21 | 22 | getCurrentThread *windows.LazyProc 23 | getThreadId *windows.LazyProc 24 | ) 25 | 26 | func init() { 27 | // Library 28 | libuser32 := windows.NewLazySystemDLL("user32.dll") 29 | libkernel32 := windows.NewLazySystemDLL("kernel32.dll") 30 | 31 | // Functions 32 | registerHotKey = libuser32.NewProc("RegisterHotKey") 33 | unregisterHotKey = libuser32.NewProc("UnregisterHotKey") 34 | postThreadMessage = libuser32.NewProc("PostThreadMessageW") 35 | 36 | getCurrentThread = libkernel32.NewProc("GetCurrentThread") 37 | getThreadId = libkernel32.NewProc("GetThreadId") 38 | } 39 | 40 | func RegisterHotKey(hwnd win.HWND, id int32, fsModifiers, vk uint32) bool { 41 | ret, _, _ := registerHotKey.Call( 42 | uintptr(hwnd), 43 | uintptr(id), 44 | uintptr(fsModifiers), 45 | uintptr(vk)) 46 | 47 | return ret != 0 48 | } 49 | 50 | func PostThreadMessage(idThread uint32, msg uint32, wParam, lParam int32) bool { 51 | ret, _, _ := postThreadMessage.Call( 52 | uintptr(idThread), 53 | uintptr(msg), 54 | uintptr(wParam), 55 | uintptr(lParam)) 56 | return ret != 0 57 | } 58 | 59 | func UnregisterHotKey(hwnd win.HWND, id int32) bool { 60 | ret, _, _ := unregisterHotKey.Call( 61 | uintptr(hwnd), 62 | uintptr(id)) 63 | 64 | return ret != 0 65 | } 66 | 67 | func GetCurrentThread() win.HANDLE { 68 | ret, _, _ := getCurrentThread.Call() 69 | return win.HANDLE(ret) 70 | } 71 | 72 | func GetThreadId(thread win.HANDLE) uint32 { 73 | ret, _, _ := getThreadId.Call(uintptr(thread)) 74 | return uint32(ret) 75 | } 76 | -------------------------------------------------------------------------------- /hotkey_test.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package hotkey_test 7 | 8 | import ( 9 | "testing" 10 | ) 11 | 12 | import "github.com/MakeNowJust/hotkey" 13 | 14 | type testRegister struct { 15 | fsModifiers hotkey.Modifier 16 | vk uint32 17 | } 18 | 19 | var registers = []testRegister{ 20 | {hotkey.Ctrl, 'A'}, 21 | {hotkey.Ctrl + hotkey.Alt, 'B'}, 22 | {hotkey.Shift, 'C'}, 23 | {hotkey.Shift * hotkey.Win, 'D'}, 24 | {hotkey.Win, hotkey.F1}, 25 | } 26 | 27 | func TestRegister(t *testing.T) { 28 | count := 0 29 | hotkey.MockRegister = func(fsModifiers, vk uint32, handle func()) (hotkey.Id, error) { 30 | if reg := registers[count]; reg.fsModifiers != hotkey.Modifier(fsModifiers) || reg.vk != vk { 31 | t.Errorf("unexpected to register call (%d, %d)", fsModifiers, vk) 32 | } 33 | count += 1 34 | return hotkey.Id(count), nil 35 | } 36 | 37 | man := hotkey.New() 38 | for _, reg := range registers { 39 | man.Register(reg.fsModifiers, reg.vk, func() { 40 | // nothing 41 | }) 42 | } 43 | if count != len(registers) { 44 | t.Error("no enough to call count register") 45 | } 46 | } 47 | 48 | func TestUnregister(t *testing.T) { 49 | ids := make([]hotkey.Id, len(registers)/2) 50 | count := 0 51 | hotkey.MockRegister = func(fsModifiers, vk uint32, handle func()) (hotkey.Id, error) { 52 | if reg := registers[count]; reg.fsModifiers != hotkey.Modifier(fsModifiers) || reg.vk != vk { 53 | t.Errorf("unexpected to register call (%d, %d)", fsModifiers, vk) 54 | } 55 | count += 1 56 | return hotkey.Id(count), nil 57 | } 58 | idx1 := 0 59 | hotkey.MockUnregister = func(id int32) { 60 | if ids[idx1] != hotkey.Id(id) { 61 | t.Errorf("unexpected to unregister call (%d)", id) 62 | } 63 | idx1 += 1 64 | } 65 | 66 | idx2 := 0 67 | man := hotkey.New() 68 | for _, reg := range registers { 69 | id, _ := man.Register(reg.fsModifiers, reg.vk, func() { 70 | // nothing 71 | }) 72 | if idx2 < len(ids) { 73 | ids[idx2] = id 74 | idx2 += 1 75 | } 76 | } 77 | if count != len(registers) { 78 | t.Error("no enough to call count register") 79 | } 80 | 81 | for _, id := range ids { 82 | man.Unregister(id) 83 | } 84 | if idx1 != idx2 { 85 | t.Error("no enough to call count unregister") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /serverImpl.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | // +build windows 7 | 8 | package hotkey 9 | 10 | import ( 11 | "fmt" 12 | "runtime" 13 | 14 | "github.com/lxn/win" 15 | ) 16 | 17 | import "github.com/MakeNowJust/hotkey/win" 18 | 19 | const ( 20 | msgRegister = iota 21 | msgUnregister 22 | msgStop 23 | ) 24 | 25 | type message struct { 26 | msgType int 27 | id int32 28 | fsModifiers, vk uint32 29 | 30 | // Channels as result notifier 31 | chId chan Id 32 | chErr chan error 33 | } 34 | 35 | type serverImpl struct { 36 | chMsg chan *message 37 | id2handle map[Id]func() 38 | threadId uint32 39 | stopFlag bool 40 | 41 | sendStopFlag bool 42 | 43 | // For debugging 44 | debug debugT 45 | } 46 | 47 | // Hotkey's id manager. It is thread safe. 48 | var globalId = func() <-chan int32 { 49 | globalId := make(chan int32, 1) 50 | go func() { 51 | for i := int32(1); ; i++ { 52 | globalId <- i 53 | } 54 | }() 55 | return globalId 56 | }() 57 | 58 | func init() { 59 | newServer = func() server { 60 | svr := new(serverImpl) 61 | svr.chMsg = make(chan *message, 100) 62 | svr.id2handle = make(map[Id]func()) 63 | svr.debug = debugT(false) 64 | 65 | chThreadId := make(chan uint32) 66 | svr.debug.Log("Start hotkey's loop") 67 | go func() { 68 | svr.debug.Log("Lock thread for win32api") 69 | runtime.LockOSThread() 70 | 71 | svr.debug.Log("Send a thread id") 72 | chThreadId <- hotkey_win.GetThreadId(hotkey_win.GetCurrentThread()) 73 | 74 | svr.debug.Log("The main of hotkey's loop") 75 | for { 76 | select { 77 | // Has message 78 | case msg := <-svr.chMsg: 79 | svr.debug.Log("Received message in hotkey's loop", msg) 80 | switch msg.msgType { 81 | case msgRegister: 82 | svr.debug.Log("Register message", msg) 83 | id := <-globalId 84 | if !hotkey_win.RegisterHotKey(0, id, msg.fsModifiers, msg.vk) { 85 | // TODO: Get system error message 86 | msg.chErr <- fmt.Errorf("failed to register hotkey {mods=%d, vk=%d}", msg.fsModifiers, msg.vk) 87 | break 88 | } 89 | defer func() { 90 | svr.debug.Log("defer Unregister", id) 91 | hotkey_win.UnregisterHotKey(0, id) 92 | }() 93 | msg.chId <- Id(id) 94 | runtime.Gosched() 95 | 96 | case msgUnregister: 97 | svr.debug.Log("Unregister message", msg) 98 | hotkey_win.UnregisterHotKey(0, msg.id) 99 | msg.chErr <- nil 100 | 101 | case msgStop: 102 | svr.debug.Log("Stop message", msg) 103 | svr.stopFlag = true 104 | svr.sendStop() 105 | msg.chErr <- nil 106 | return 107 | } 108 | 109 | // No message 110 | default: 111 | svr.debug.Log("Wait hotkey message") 112 | var msg win.MSG 113 | res := win.GetMessage(&msg, 0, 0, 0) 114 | 115 | if res == 0 || res == -1 { 116 | // TODO: Get system error message 117 | svr.stopFlag = true 118 | svr.sendStop() 119 | return 120 | } 121 | 122 | switch msg.Message { 123 | // Hotkey's command 124 | case win.WM_HOTKEY: 125 | svr.debug.Log("WM_HOTKEY", msg.WParam) 126 | if handle, ok := svr.id2handle[Id(msg.WParam)]; ok { 127 | handle() 128 | } 129 | 130 | default: 131 | svr.debug.Log("Other message") 132 | win.TranslateMessage(&msg) 133 | win.DispatchMessage(&msg) 134 | } 135 | } 136 | } 137 | }() 138 | 139 | // Recive a thread id 140 | svr.threadId = <-chThreadId 141 | 142 | return svr 143 | } 144 | } 145 | 146 | func (svr *serverImpl) register(fsModifiers, vk uint32, handle func()) (id Id, err error) { 147 | if svr.stopFlag { 148 | err = fmt.Errorf("already stoped hotkey's loop") 149 | return 150 | } 151 | 152 | var msg message 153 | msg.msgType = msgRegister 154 | msg.fsModifiers = fsModifiers 155 | msg.vk = vk 156 | 157 | msg.chId = make(chan Id) 158 | msg.chErr = make(chan error) 159 | 160 | svr.chMsg <- &msg 161 | hotkey_win.PostThreadMessage(svr.threadId, win.WM_USER, 0, 0) 162 | 163 | // Wait 164 | select { 165 | case id = <-msg.chId: 166 | svr.debug.Log("Register success", id) 167 | svr.id2handle[id] = handle 168 | case err = <-msg.chErr: 169 | } 170 | return 171 | } 172 | 173 | func (svr *serverImpl) unregister(id int32) { 174 | if svr.stopFlag { 175 | return 176 | } 177 | 178 | var msg message 179 | msg.msgType = msgUnregister 180 | msg.id = id 181 | 182 | msg.chErr = make(chan error) 183 | 184 | svr.chMsg <- &msg 185 | hotkey_win.PostThreadMessage(svr.threadId, win.WM_USER, 0, 0) 186 | 187 | // Wait 188 | <-msg.chErr 189 | 190 | svr.debug.Log("Unregister done") 191 | } 192 | 193 | func (svr *serverImpl) stop() { 194 | if svr.stopFlag { 195 | return 196 | } 197 | 198 | var msg message 199 | msg.msgType = msgStop 200 | 201 | msg.chErr = make(chan error) 202 | 203 | svr.chMsg <- &msg 204 | hotkey_win.PostThreadMessage(svr.threadId, win.WM_USER, 0, 0) 205 | 206 | // Wait 207 | <-msg.chErr 208 | 209 | svr.debug.Log("Stop done") 210 | } 211 | 212 | func (svr *serverImpl) sendStop() { 213 | if svr.sendStopFlag { 214 | svr.debug.Log("sendStop") 215 | svr.sendStopFlag = true 216 | 217 | for len(svr.chMsg) >= 1 { 218 | msg := <-svr.chMsg 219 | 220 | if msg.msgType == msgRegister { 221 | msg.chErr <- fmt.Errorf("already stoped hotkey's loop") 222 | } else { 223 | msg.chErr <- nil 224 | } 225 | } 226 | } 227 | } 228 | 229 | func (svr *serverImpl) isStop() bool { 230 | return svr.stopFlag 231 | } 232 | 233 | func (svr *serverImpl) useDebugLog() { 234 | svr.debug = debugT(true) 235 | } 236 | -------------------------------------------------------------------------------- /virtual_key.go: -------------------------------------------------------------------------------- 1 | // This software is released under the MIT License. 2 | // 3 | // 4 | // Copyright (c) 2014-2023 Hiroya Fujinami (a.k.a. TSUYUSATO "MakeNowJust" Kitsune) 5 | 6 | package hotkey 7 | 8 | // These are virtual key codes. 9 | // See also, http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 10 | const ( 11 | LBUTTON = uint32(0x01) 12 | RBUTTON = uint32(0x02) 13 | CANCEL = uint32(0x03) 14 | MBUTTON = uint32(0x04) 15 | XBUTTON1 = uint32(0x05) 16 | XBUTTON2 = uint32(0x06) 17 | BACK = uint32(0x08) 18 | TAB = uint32(0x09) 19 | CLEAR = uint32(0x0C) 20 | RETURN = uint32(0x0D) 21 | SHIFT = uint32(0x10) 22 | CONTROL = uint32(0x11) 23 | MENU = uint32(0x12) 24 | PAUSE = uint32(0x13) 25 | CAPITAL = uint32(0x14) 26 | KANA = uint32(0x15) 27 | HANGUEL = uint32(0x15) 28 | HANGUL = uint32(0x15) 29 | JUNJA = uint32(0x17) 30 | FINAL = uint32(0x18) 31 | HANJA = uint32(0x19) 32 | KANJI = uint32(0x19) 33 | ESCAPE = uint32(0x1B) 34 | CONVERT = uint32(0x1C) 35 | NONCONVERT = uint32(0x1D) 36 | ACCEPT = uint32(0x1E) 37 | MODECHANGE = uint32(0x1F) 38 | SPACE = uint32(0x20) 39 | PRIOR = uint32(0x21) 40 | NEXT = uint32(0x22) 41 | END = uint32(0x23) 42 | HOME = uint32(0x24) 43 | LEFT = uint32(0x25) 44 | UP = uint32(0x26) 45 | RIGHT = uint32(0x27) 46 | DOWN = uint32(0x28) 47 | SELECT = uint32(0x29) 48 | PRINT = uint32(0x2A) 49 | EXECUTE = uint32(0x2B) 50 | SNAPSHOT = uint32(0x2C) 51 | INSERT = uint32(0x2D) 52 | DELETE = uint32(0x2E) 53 | HELP = uint32(0x2F) 54 | LWIN = uint32(0x5B) 55 | RWIN = uint32(0x5C) 56 | APPS = uint32(0x5D) 57 | SLEEP = uint32(0x5F) 58 | NUMPAD0 = uint32(0x60) 59 | NUMPAD1 = uint32(0x61) 60 | NUMPAD2 = uint32(0x62) 61 | NUMPAD3 = uint32(0x63) 62 | NUMPAD4 = uint32(0x64) 63 | NUMPAD5 = uint32(0x65) 64 | NUMPAD6 = uint32(0x66) 65 | NUMPAD7 = uint32(0x67) 66 | NUMPAD8 = uint32(0x68) 67 | NUMPAD9 = uint32(0x69) 68 | MULTIPLY = uint32(0x6A) 69 | ADD = uint32(0x6B) 70 | SEPARATOR = uint32(0x6C) 71 | SUBTRACT = uint32(0x6D) 72 | DECIMAL = uint32(0x6E) 73 | DIVIDE = uint32(0x6F) 74 | F1 = uint32(0x70) 75 | F2 = uint32(0x71) 76 | F3 = uint32(0x72) 77 | F4 = uint32(0x73) 78 | F5 = uint32(0x74) 79 | F6 = uint32(0x75) 80 | F7 = uint32(0x76) 81 | F8 = uint32(0x77) 82 | F9 = uint32(0x78) 83 | F10 = uint32(0x79) 84 | F11 = uint32(0x7A) 85 | F12 = uint32(0x7B) 86 | F13 = uint32(0x7C) 87 | F14 = uint32(0x7D) 88 | F15 = uint32(0x7E) 89 | F16 = uint32(0x7F) 90 | F17 = uint32(0x80) 91 | F18 = uint32(0x81) 92 | F19 = uint32(0x82) 93 | F20 = uint32(0x83) 94 | F21 = uint32(0x84) 95 | F22 = uint32(0x85) 96 | F23 = uint32(0x86) 97 | F24 = uint32(0x87) 98 | NUMLOCK = uint32(0x90) 99 | SCROLL = uint32(0x91) 100 | LSHIFT = uint32(0xA0) 101 | RSHIFT = uint32(0xA1) 102 | LCONTROL = uint32(0xA2) 103 | RCONTROL = uint32(0xA3) 104 | LMENU = uint32(0xA4) 105 | RMENU = uint32(0xA5) 106 | BROWSER_BACK = uint32(0xA6) 107 | BROWSER_FORWARD = uint32(0xA7) 108 | BROWSER_REFRESH = uint32(0xA8) 109 | BROWSER_STOP = uint32(0xA9) 110 | BROWSER_SEARCH = uint32(0xAA) 111 | BROWSER_FAVORITES = uint32(0xAB) 112 | BROWSER_HOME = uint32(0xAC) 113 | VOLUME_MUTE = uint32(0xAD) 114 | VOLUME_DOWN = uint32(0xAE) 115 | VOLUME_UP = uint32(0xAF) 116 | MEDIA_NEXT_TRACK = uint32(0xB0) 117 | MEDIA_PREV_TRACK = uint32(0xB1) 118 | MEDIA_STOP = uint32(0xB2) 119 | MEDIA_PLAY_PAUSE = uint32(0xB3) 120 | LAUNCH_MAIL = uint32(0xB4) 121 | LAUNCH_MEDIA_SELECT = uint32(0xB5) 122 | LAUNCH_APP1 = uint32(0xB6) 123 | LAUNCH_APP2 = uint32(0xB7) 124 | OEM_1 = uint32(0xBA) 125 | OEM_PLUS = uint32(0xBB) 126 | OEM_COMMA = uint32(0xBC) 127 | OEM_MINUS = uint32(0xBD) 128 | OEM_PERIOD = uint32(0xBE) 129 | OEM_2 = uint32(0xBF) 130 | OEM_3 = uint32(0xC0) 131 | OEM_4 = uint32(0xDB) 132 | OEM_5 = uint32(0xDC) 133 | OEM_6 = uint32(0xDD) 134 | OEM_7 = uint32(0xDE) 135 | OEM_8 = uint32(0xDF) 136 | OEM_102 = uint32(0xE2) 137 | PROCESSKEY = uint32(0xE5) 138 | PACKET = uint32(0xE7) 139 | ATTN = uint32(0xF6) 140 | CRSEL = uint32(0xF7) 141 | EXSEL = uint32(0xF8) 142 | EREOF = uint32(0xF9) 143 | PLAY = uint32(0xFA) 144 | ZOOM = uint32(0xFB) 145 | NONAME = uint32(0xFC) 146 | PA1 = uint32(0xFD) 147 | OEM_CLEAR = uint32(0xFE) 148 | ) 149 | --------------------------------------------------------------------------------