├── README.md ├── go.mod ├── vendor ├── modules.txt └── golang.design │ └── x │ ├── hotkey │ ├── .gitignore │ ├── mainthread │ │ ├── doc.go │ │ ├── os.go │ │ ├── os_darwin.m │ │ └── os_darwin.go │ ├── hotkey_nocgo.go │ ├── LICENSE │ ├── hotkey_darwin.m │ ├── hotkey_linux.c │ ├── README.md │ ├── internal │ │ └── win │ │ │ └── hotkey.go │ ├── hotkey_darwin.go │ ├── hotkey_linux.go │ ├── hotkey_windows.go │ └── hotkey.go │ └── mainthread │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ └── mainthread.go ├── go.sum ├── .gitignore ├── main.go └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | ### Keyboard App Switcher 2 | 3 | A simple Go based background process for switching between specific apps using keyboard shortcuts. 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module josephwoodward.com/appswitcher 2 | 3 | go 1.22.0 4 | 5 | require golang.design/x/hotkey v0.4.1 6 | 7 | require golang.design/x/mainthread v0.3.0 // indirect 8 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # golang.design/x/hotkey v0.4.1 2 | ## explicit; go 1.17 3 | golang.design/x/hotkey 4 | golang.design/x/hotkey/internal/win 5 | golang.design/x/hotkey/mainthread 6 | # golang.design/x/mainthread v0.3.0 7 | ## explicit; go 1.16 8 | golang.design/x/mainthread 9 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /vendor/golang.design/x/mainthread/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/mainthread/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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 6 | 7 | // Package mainthread wrapps the golang.design/x/mainthread, and 8 | // provides a different implementation for macOS so that it can 9 | // handle main thread events for the NSApplication. 10 | package mainthread 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.design/x/hotkey v0.4.1 h1:zLP/2Pztl4WjyxURdW84GoZ5LUrr6hr69CzJFJ5U1go= 2 | golang.design/x/hotkey v0.4.1/go.mod h1:M8SGcwFYHnKRa83FpTFQoZvPO5vVT+kWPztFqTQKmXA= 3 | golang.design/x/mainthread v0.3.0 h1:UwFus0lcPodNpMOGoQMe87jSFwbSsEY//CA7yVmu4j8= 4 | golang.design/x/mainthread v0.3.0/go.mod h1:vYX7cF2b3pTJMGM/hc13NmN6kblKnf4/IyvHeu259L0= 5 | golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc= 6 | golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/hotkey_nocgo.go: -------------------------------------------------------------------------------- 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 6 | 7 | //go:build !windows && !cgo 8 | 9 | package hotkey 10 | 11 | type platformHotkey struct{} 12 | 13 | // Modifier represents a modifier 14 | type Modifier uint32 15 | 16 | // Key represents a key. 17 | type Key uint8 18 | 19 | func (hk *Hotkey) register() error { 20 | panic("hotkey: cannot use when CGO_ENABLED=0") 21 | } 22 | 23 | // unregister deregisteres a system hotkey. 24 | func (hk *Hotkey) unregister() error { 25 | panic("hotkey: cannot use when CGO_ENABLED=0") 26 | } 27 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/mainthread/os.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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 6 | 7 | //go:build windows || linux || (darwin && !cgo) 8 | 9 | package mainthread 10 | 11 | import "golang.design/x/mainthread" 12 | 13 | // Call calls f on the main thread and blocks until f finishes. 14 | func Call(f func()) { mainthread.Call(f) } 15 | 16 | // Init initializes the functionality of running arbitrary subsequent functions be called on the main system thread. 17 | // 18 | // Init must be called in the main.main function. 19 | func Init(main func()) { mainthread.Init(main) } 20 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/mainthread/os_darwin.m: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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 6 | 7 | //go:build darwin 8 | 9 | #include 10 | #import 11 | 12 | extern void dispatchMainFuncs(); 13 | 14 | void wakeupMainThread(void) { 15 | dispatch_async(dispatch_get_main_queue(), ^{ 16 | dispatchMainFuncs(); 17 | }); 18 | } 19 | 20 | // The following three lines of code must run on the main thread. 21 | // It must handle it using golang.design/x/mainthread. 22 | // 23 | // inspired from here: https://github.com/cehoffman/dotfiles/blob/4be8e893517e970d40746a9bdc67fe5832dd1c33/os/mac/iTerm2HotKey.m 24 | void os_main(void) { 25 | [NSApplication sharedApplication]; 26 | [NSApp disableRelaunchOnLogin]; 27 | [NSApp run]; 28 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os/exec" 7 | 8 | "golang.design/x/hotkey" 9 | "golang.design/x/hotkey/mainthread" 10 | ) 11 | 12 | var keys = map[hotkey.Key]string{ 13 | hotkey.KeyQ: "/Applications/Kitty.app/", 14 | hotkey.KeyW: "/Applications/Google Chrome.app/", 15 | hotkey.KeyE: "/Applications/Slack.app/", 16 | hotkey.KeyR: "/Applications/Spotify.app/", 17 | hotkey.KeyZ: "/Applications/zoom.us.app/", 18 | } 19 | 20 | func main() { mainthread.Init(fn) } 21 | 22 | func fn() { 23 | for key, app := range keys { 24 | hk := hotkey.New([]hotkey.Modifier{hotkey.ModCmd, hotkey.ModShift}, key) 25 | if err := hk.Register(); err != nil { 26 | panic(err) 27 | } 28 | fmt.Println("registered", app) 29 | 30 | go func(app string) { 31 | for range hk.Keydown() { 32 | _ = exec.Command("open", app).Run() 33 | log.Println("Switching to", app) 34 | } 35 | }(app) 36 | } 37 | 38 | select {} 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Joseph Woodward 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Changkun Ou 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/golang.design/x/mainthread/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Changkun Ou 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/mainthread/os_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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 6 | 7 | //go:build darwin 8 | 9 | package mainthread 10 | 11 | /* 12 | #cgo CFLAGS: -x objective-c 13 | #cgo LDFLAGS: -framework Cocoa 14 | #import 15 | #import 16 | 17 | extern void os_main(void); 18 | extern void wakeupMainThread(void); 19 | static bool isMainThread() { 20 | return [NSThread isMainThread]; 21 | } 22 | */ 23 | import "C" 24 | import ( 25 | "os" 26 | "runtime" 27 | ) 28 | 29 | func init() { 30 | runtime.LockOSThread() 31 | } 32 | 33 | // Call calls f on the main thread and blocks until f finishes. 34 | func Call(f func()) { 35 | if C.isMainThread() { 36 | f() 37 | return 38 | } 39 | go func() { 40 | mainFuncs <- f 41 | C.wakeupMainThread() 42 | }() 43 | } 44 | 45 | // Init initializes the functionality of running arbitrary subsequent functions be called on the main system thread. 46 | // 47 | // Init must be called in the main.main function. 48 | func Init(f func()) { 49 | go func() { 50 | f() 51 | os.Exit(0) 52 | }() 53 | 54 | C.os_main() 55 | } 56 | 57 | var mainFuncs = make(chan func(), 1) 58 | 59 | //export dispatchMainFuncs 60 | func dispatchMainFuncs() { 61 | for { 62 | select { 63 | case f := <-mainFuncs: 64 | f() 65 | default: 66 | return 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/hotkey_darwin.m: -------------------------------------------------------------------------------- 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 6 | 7 | //go:build darwin 8 | 9 | #include 10 | #import 11 | #import 12 | extern void keydownCallback(uintptr_t handle); 13 | extern void keyupCallback(uintptr_t handle); 14 | 15 | static OSStatus 16 | keydownHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) { 17 | EventHotKeyID k; 18 | GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(k), NULL, &k); 19 | keydownCallback((uintptr_t)k.id); // use id as handle 20 | return noErr; 21 | } 22 | 23 | static OSStatus 24 | keyupHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) { 25 | EventHotKeyID k; 26 | GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(k), NULL, &k); 27 | keyupCallback((uintptr_t)k.id); // use id as handle 28 | return noErr; 29 | } 30 | 31 | // registerHotkeyWithCallback registers a global system hotkey for callbacks. 32 | int registerHotKey(int mod, int key, uintptr_t handle, EventHotKeyRef* ref) { 33 | __block OSStatus s; 34 | dispatch_sync(dispatch_get_main_queue(), ^{ 35 | EventTypeSpec keydownEvent; 36 | keydownEvent.eventClass = kEventClassKeyboard; 37 | keydownEvent.eventKind = kEventHotKeyPressed; 38 | EventTypeSpec keyupEvent; 39 | keyupEvent.eventClass = kEventClassKeyboard; 40 | keyupEvent.eventKind = kEventHotKeyReleased; 41 | InstallApplicationEventHandler( 42 | &keydownHandler, 1, &keydownEvent, NULL, NULL 43 | ); 44 | InstallApplicationEventHandler( 45 | &keyupHandler, 1, &keyupEvent, NULL, NULL 46 | ); 47 | 48 | EventHotKeyID hkid = {.id = handle}; 49 | s = RegisterEventHotKey( 50 | key, mod, hkid, GetApplicationEventTarget(), 0, ref 51 | ); 52 | }); 53 | if (s != noErr) { 54 | return -1; 55 | } 56 | return 0; 57 | } 58 | 59 | int unregisterHotKey(EventHotKeyRef ref) { 60 | OSStatus s = UnregisterEventHotKey(ref); 61 | if (s != noErr) { 62 | return -1; 63 | } 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/hotkey_linux.c: -------------------------------------------------------------------------------- 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 6 | 7 | //go:build linux 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | extern void hotkeyDown(uintptr_t hkhandle); 15 | extern void hotkeyUp(uintptr_t hkhandle); 16 | 17 | int displayTest() { 18 | Display* d = NULL; 19 | for (int i = 0; i < 42; i++) { 20 | d = XOpenDisplay(0); 21 | if (d == NULL) continue; 22 | break; 23 | } 24 | if (d == NULL) { 25 | return -1; 26 | } 27 | return 0; 28 | } 29 | 30 | // FIXME: handle bad access properly. 31 | // int handleErrors( Display* dpy, XErrorEvent* pErr ) 32 | // { 33 | // printf("X Error Handler called, values: %d/%lu/%d/%d/%d\n", 34 | // pErr->type, 35 | // pErr->serial, 36 | // pErr->error_code, 37 | // pErr->request_code, 38 | // pErr->minor_code ); 39 | // if( pErr->request_code == 33 ){ // 33 (X_GrabKey) 40 | // if( pErr->error_code == BadAccess ){ 41 | // printf("ERROR: key combination already grabbed by another client.\n"); 42 | // return 0; 43 | // } 44 | // } 45 | // return 0; 46 | // } 47 | 48 | // waitHotkey blocks until the hotkey is triggered. 49 | // this function crashes the program if the hotkey already grabbed by others. 50 | int waitHotkey(uintptr_t hkhandle, unsigned int mod, int key) { 51 | Display* d = NULL; 52 | for (int i = 0; i < 42; i++) { 53 | d = XOpenDisplay(0); 54 | if (d == NULL) continue; 55 | break; 56 | } 57 | if (d == NULL) { 58 | return -1; 59 | } 60 | int keycode = XKeysymToKeycode(d, key); 61 | XGrabKey(d, keycode, mod, DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync); 62 | XSelectInput(d, DefaultRootWindow(d), KeyPressMask); 63 | XEvent ev; 64 | while(1) { 65 | XNextEvent(d, &ev); 66 | switch(ev.type) { 67 | case KeyPress: 68 | hotkeyDown(hkhandle); 69 | continue; 70 | case KeyRelease: 71 | hotkeyUp(hkhandle); 72 | XUngrabKey(d, keycode, mod, DefaultRootWindow(d)); 73 | XCloseDisplay(d); 74 | return 0; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /vendor/golang.design/x/mainthread/README.md: -------------------------------------------------------------------------------- 1 | # mainthread [![PkgGoDev](https://pkg.go.dev/badge/golang.design/x/mainthread)](https://pkg.go.dev/golang.design/x/mainthread) ![mainthread](https://github.com/golang-design/mainthread/workflows/mainthread/badge.svg?branch=main) ![](https://changkun.de/urlstat?mode=github&repo=golang-design/mainthread) 2 | 3 | schedule functions to run on the main thread 4 | 5 | ```go 6 | import "golang.design/x/mainthread" 7 | ``` 8 | 9 | ## Features 10 | 11 | - Main thread scheduling 12 | - Schedule functions without memory allocation 13 | 14 | ## API Usage 15 | 16 | Package mainthread offers facilities to schedule functions 17 | on the main thread. To use this package properly, one must 18 | call `mainthread.Init` from the main package. For example: 19 | 20 | ```go 21 | package main 22 | 23 | import "golang.design/x/mainthread" 24 | 25 | func main() { mainthread.Init(fn) } 26 | 27 | // fn is the actual main function 28 | func fn() { 29 | // ... do stuff ... 30 | 31 | // mainthread.Call returns when f1 returns. Note that if f1 blocks 32 | // it will also block the execution of any subsequent calls on the 33 | // main thread. 34 | mainthread.Call(f1) 35 | 36 | // ... do stuff ... 37 | 38 | 39 | // mainthread.Go returns immediately and f2 is scheduled to be 40 | // executed in the future. 41 | mainthread.Go(f2) 42 | 43 | // ... do stuff ... 44 | } 45 | 46 | func f1() { ... } 47 | func f2() { ... } 48 | ``` 49 | 50 | If the given function triggers a panic, and called via `mainthread.Call`, 51 | then the panic will be propagated to the same goroutine. One can capture 52 | that panic, when possible: 53 | 54 | ```go 55 | defer func() { 56 | if r := recover(); r != nil { 57 | println(r) 58 | } 59 | }() 60 | 61 | mainthread.Call(func() { ... }) // if panic 62 | ``` 63 | 64 | If the given function triggers a panic, and called via `mainthread.Go`, 65 | then the panic will be cached internally, until a call to the `Error()` method: 66 | 67 | ```go 68 | mainthread.Go(func() { ... }) // if panics 69 | 70 | // ... do stuff ... 71 | 72 | if err := mainthread.Error(); err != nil { // can be captured here. 73 | println(err) 74 | } 75 | ``` 76 | 77 | Note that a panic happens before `mainthread.Error()` returning the 78 | panicked error. If one needs to guarantee `mainthread.Error()` indeed 79 | captured the panic, a dummy function can be used as synchornization: 80 | 81 | ```go 82 | mainthread.Go(func() { panic("die") }) // if panics 83 | mainthread.Call(func() {}) // for execution synchronization 84 | err := mainthread.Error() // err must be non-nil 85 | ``` 86 | 87 | 88 | It is possible to cache up to a maximum of 42 panicked errors. 89 | More errors are ignored. 90 | 91 | ## When do you need this package? 92 | 93 | Read this to learn more about the design purpose of this package: 94 | https://golang.design/research/zero-alloc-call-sched/ 95 | 96 | ## Who is using this package? 97 | 98 | The initial purpose of building this package is to support writing 99 | graphical applications in Go. To know projects that are using this 100 | package, check our [wiki](https://github.com/golang-design/mainthread/wiki) 101 | page. 102 | 103 | 104 | ## License 105 | 106 | MIT | © 2021 The golang.design Initiative Authors, written by [Changkun Ou](https://changkun.de). -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/README.md: -------------------------------------------------------------------------------- 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 | global hotkey shortcut so that an application can be notified if a user 18 | triggers the desired hotkey. A hotkey must be a combination of modifiers 19 | and a single key. 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "log" 26 | 27 | "golang.design/x/hotkey" 28 | "golang.design/x/hotkey/mainthread" 29 | ) 30 | 31 | func main() { mainthread.Init(fn) } // Not necessary when use in Fyne, Ebiten or Gio. 32 | func fn() { 33 | hk := hotkey.New([]hotkey.Modifier{hotkey.ModCtrl, hotkey.ModShift}, hotkey.KeyS) 34 | err := hk.Register() 35 | if err != nil { 36 | log.Fatalf("hotkey: failed to register hotkey: %v", err) 37 | return 38 | } 39 | 40 | log.Printf("hotkey: %v is registered\n", hk) 41 | <-hk.Keydown() 42 | log.Printf("hotkey: %v is down\n", hk) 43 | <-hk.Keyup() 44 | log.Printf("hotkey: %v is up\n", hk) 45 | hk.Unregister() 46 | log.Printf("hotkey: %v is unregistered\n", hk) 47 | } 48 | ``` 49 | 50 | Note platform specific details: 51 | 52 | - On macOS, due to the OS restriction (other platforms does not have this 53 | restriction), hotkey events must be handled on the "main thread". 54 | Therefore, in order to use this package properly, one must start an OS 55 | main event loop on the main thread, For self-contained applications, 56 | using [golang.design/x/hotkey/mainthread](https://pkg.go.dev/golang.design/x/hotkey/mainthread) 57 | is possible. It is uncessary or applications based on other GUI frameworks, 58 | such as fyne, ebiten, or Gio. See the "[./examples](./examples)" folder 59 | for more examples. 60 | - On Linux (X11), when AutoRepeat is enabled in the X server, the Keyup 61 | is triggered automatically and continuously as Keydown continues. 62 | - On Linux (X11), some keys may be mapped to multiple Mod keys. To 63 | correctly register the key combination, one must use the correct 64 | underlying keycode combination. For example, a regular Ctrl+Alt+S 65 | might be registered as: Ctrl+Mod2+Mod4+S. 66 | - If this package did not include a desired key, one can always provide 67 | the keycode to the API. For example, if a key code is 0x15, then the 68 | corresponding key is `hotkey.Key(0x15)`. 69 | 70 | ## Examples 71 | 72 | | Description | Folder | 73 | |:------------|:------:| 74 | | A minimum example | [minimum](./examples/minimum/main.go) | 75 | | Register multiple hotkeys | [multiple](./examples/multiple/main.go) | 76 | | A example to use in GLFW | [glfw](./examples/glfw/main.go) | 77 | | A example to use in Fyne | [fyne](./examples/fyne/main.go) | 78 | | A example to use in Ebiten | [ebiten](./examples/ebiten/main.go) | 79 | | A example to use in Gio | [gio](./examples/gio/main.go) | 80 | 81 | ## Who is using this package? 82 | 83 | The main purpose of building this package is to support the 84 | [midgard](https://changkun.de/s/midgard) project. 85 | 86 | To know more projects, check our [wiki](https://github.com/golang-design/hotkey/wiki) page. 87 | 88 | ## License 89 | 90 | MIT | © 2021 The golang.design Initiative Authors, written by [Changkun Ou](https://changkun.de). -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/internal/win/hotkey.go: -------------------------------------------------------------------------------- 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 6 | 7 | //go:build windows 8 | // +build windows 9 | 10 | package win 11 | 12 | import ( 13 | "syscall" 14 | "unsafe" 15 | ) 16 | 17 | var ( 18 | user32 = syscall.NewLazyDLL("user32") 19 | registerHotkey = user32.NewProc("RegisterHotKey") 20 | unregisterHotkey = user32.NewProc("UnregisterHotKey") 21 | getMessage = user32.NewProc("GetMessageW") 22 | peekMessage = user32.NewProc("PeekMessageA") 23 | sendMessage = user32.NewProc("SendMessageW") 24 | getAsyncKeyState = user32.NewProc("GetAsyncKeyState") 25 | quitMessage = user32.NewProc("PostQuitMessage") 26 | ) 27 | 28 | // RegisterHotKey defines a system-wide hot key. 29 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey 30 | func RegisterHotKey(hwnd, id uintptr, mod uintptr, k uintptr) (bool, error) { 31 | ret, _, err := registerHotkey.Call( 32 | hwnd, id, mod, k, 33 | ) 34 | return ret != 0, err 35 | } 36 | 37 | // UnregisterHotKey frees a hot key previously registered by the calling 38 | // thread. 39 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unregisterhotkey 40 | func UnregisterHotKey(hwnd, id uintptr) (bool, error) { 41 | ret, _, err := unregisterHotkey.Call(hwnd, id) 42 | return ret != 0, err 43 | } 44 | 45 | // MSG contains message information from a thread's message queue. 46 | // 47 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-msg 48 | type MSG struct { 49 | HWnd uintptr 50 | Message uint32 51 | WParam uintptr 52 | LParam uintptr 53 | Time uint32 54 | Pt struct { //POINT 55 | x, y int32 56 | } 57 | } 58 | 59 | // SendMessage sends the specified message to a window or windows. 60 | // The SendMessage function calls the window procedure for the specified 61 | // window and does not return until the window procedure has processed 62 | // the message. 63 | // The return value specifies the result of the message processing; 64 | // it depends on the message sent. 65 | // 66 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage 67 | func SendMessage(hwnd uintptr, msg uint32, wParam, lParam uintptr) uintptr { 68 | ret, _, _ := sendMessage.Call( 69 | hwnd, 70 | uintptr(msg), 71 | wParam, 72 | lParam, 73 | ) 74 | 75 | return ret 76 | } 77 | 78 | // GetMessage retrieves a message from the calling thread's message 79 | // queue. The function dispatches incoming sent messages until a posted 80 | // message is available for retrieval. 81 | // 82 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage 83 | func GetMessage(msg *MSG, hWnd uintptr, msgFilterMin, msgFilterMax uint32) bool { 84 | ret, _, _ := getMessage.Call( 85 | uintptr(unsafe.Pointer(msg)), 86 | hWnd, 87 | uintptr(msgFilterMin), 88 | uintptr(msgFilterMax), 89 | ) 90 | 91 | return ret != 0 92 | } 93 | 94 | // PeekMessage dispatches incoming sent messages, checks the thread message 95 | // queue for a posted message, and retrieves the message (if any exist). 96 | // 97 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-peekmessagea 98 | func PeekMessage(msg *MSG, hWnd uintptr, msgFilterMin, msgFilterMax uint32) bool { 99 | ret, _, _ := peekMessage.Call( 100 | uintptr(unsafe.Pointer(msg)), 101 | hWnd, 102 | uintptr(msgFilterMin), 103 | uintptr(msgFilterMax), 104 | 0, // PM_NOREMOVE 105 | ) 106 | 107 | return ret != 0 108 | } 109 | 110 | // PostQuitMessage indicates to the system that a thread has made 111 | // a request to terminate (quit). It is typically used in response 112 | // to a WM_DESTROY message. 113 | // 114 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postquitmessage 115 | func PostQuitMessage(exitCode int) { 116 | quitMessage.Call(uintptr(exitCode)) 117 | } 118 | 119 | func GetAsyncKeyState(keycode int) uintptr { 120 | ret, _, _ := getAsyncKeyState.Call(uintptr(keycode)) 121 | return ret 122 | } 123 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/hotkey_darwin.go: -------------------------------------------------------------------------------- 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 6 | 7 | //go:build darwin 8 | 9 | package hotkey 10 | 11 | /* 12 | #cgo CFLAGS: -x objective-c 13 | #cgo LDFLAGS: -framework Cocoa -framework Carbon 14 | #include 15 | #import 16 | #import 17 | 18 | extern void keydownCallback(uintptr_t handle); 19 | extern void keyupCallback(uintptr_t handle); 20 | int registerHotKey(int mod, int key, uintptr_t handle, EventHotKeyRef* ref); 21 | int unregisterHotKey(EventHotKeyRef ref); 22 | */ 23 | import "C" 24 | import ( 25 | "errors" 26 | "runtime/cgo" 27 | "sync" 28 | ) 29 | 30 | // Hotkey is a combination of modifiers and key to trigger an event 31 | type platformHotkey struct { 32 | mu sync.Mutex 33 | registered bool 34 | hkref C.EventHotKeyRef 35 | } 36 | 37 | func (hk *Hotkey) register() error { 38 | hk.mu.Lock() 39 | defer hk.mu.Unlock() 40 | if hk.registered { 41 | return errors.New("hotkey already registered") 42 | } 43 | 44 | // Note: we use handle number as hotkey id in the C side. 45 | // A cgo handle could ran out of space, but since in hotkey purpose 46 | // we won't have that much number of hotkeys. So this should be fine. 47 | 48 | h := cgo.NewHandle(hk) 49 | var mod Modifier 50 | for _, m := range hk.mods { 51 | mod += m 52 | } 53 | 54 | ret := C.registerHotKey(C.int(mod), C.int(hk.key), C.uintptr_t(h), &hk.hkref) 55 | if ret == C.int(-1) { 56 | return errors.New("failed to register the hotkey") 57 | } 58 | 59 | hk.registered = true 60 | return nil 61 | } 62 | 63 | func (hk *Hotkey) unregister() error { 64 | hk.mu.Lock() 65 | defer hk.mu.Unlock() 66 | if !hk.registered { 67 | return errors.New("hotkey is not registered") 68 | } 69 | 70 | ret := C.unregisterHotKey(hk.hkref) 71 | if ret == C.int(-1) { 72 | return errors.New("failed to unregister the current hotkey") 73 | } 74 | hk.registered = false 75 | return nil 76 | } 77 | 78 | //export keydownCallback 79 | func keydownCallback(h uintptr) { 80 | hk := cgo.Handle(h).Value().(*Hotkey) 81 | hk.keydownIn <- Event{} 82 | } 83 | 84 | //export keyupCallback 85 | func keyupCallback(h uintptr) { 86 | hk := cgo.Handle(h).Value().(*Hotkey) 87 | hk.keyupIn <- Event{} 88 | } 89 | 90 | // Modifier represents a modifier. 91 | // See: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h 92 | type Modifier uint32 93 | 94 | // All kinds of Modifiers 95 | const ( 96 | ModCtrl Modifier = 0x1000 97 | ModShift Modifier = 0x200 98 | ModOption Modifier = 0x800 99 | ModCmd Modifier = 0x100 100 | ) 101 | 102 | // Key represents a key. 103 | // See: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h 104 | type Key uint8 105 | 106 | // All kinds of keys 107 | const ( 108 | KeySpace Key = 49 109 | Key1 Key = 18 110 | Key2 Key = 19 111 | Key3 Key = 20 112 | Key4 Key = 21 113 | Key5 Key = 23 114 | Key6 Key = 22 115 | Key7 Key = 26 116 | Key8 Key = 28 117 | Key9 Key = 25 118 | Key0 Key = 29 119 | KeyA Key = 0 120 | KeyB Key = 11 121 | KeyC Key = 8 122 | KeyD Key = 2 123 | KeyE Key = 14 124 | KeyF Key = 3 125 | KeyG Key = 5 126 | KeyH Key = 4 127 | KeyI Key = 34 128 | KeyJ Key = 38 129 | KeyK Key = 40 130 | KeyL Key = 37 131 | KeyM Key = 46 132 | KeyN Key = 45 133 | KeyO Key = 31 134 | KeyP Key = 35 135 | KeyQ Key = 12 136 | KeyR Key = 15 137 | KeyS Key = 1 138 | KeyT Key = 17 139 | KeyU Key = 32 140 | KeyV Key = 9 141 | KeyW Key = 13 142 | KeyX Key = 7 143 | KeyY Key = 16 144 | KeyZ Key = 6 145 | 146 | KeyReturn Key = 0x24 147 | KeyEscape Key = 0x35 148 | KeyDelete Key = 0x33 149 | KeyTab Key = 0x30 150 | 151 | KeyLeft Key = 0x7B 152 | KeyRight Key = 0x7C 153 | KeyUp Key = 0x7E 154 | KeyDown Key = 0x7D 155 | 156 | KeyF1 Key = 0x7A 157 | KeyF2 Key = 0x78 158 | KeyF3 Key = 0x63 159 | KeyF4 Key = 0x76 160 | KeyF5 Key = 0x60 161 | KeyF6 Key = 0x61 162 | KeyF7 Key = 0x62 163 | KeyF8 Key = 0x64 164 | KeyF9 Key = 0x65 165 | KeyF10 Key = 0x6D 166 | KeyF11 Key = 0x67 167 | KeyF12 Key = 0x6F 168 | KeyF13 Key = 0x69 169 | KeyF14 Key = 0x6B 170 | KeyF15 Key = 0x71 171 | KeyF16 Key = 0x6A 172 | KeyF17 Key = 0x40 173 | KeyF18 Key = 0x4F 174 | KeyF19 Key = 0x50 175 | KeyF20 Key = 0x5A 176 | ) 177 | -------------------------------------------------------------------------------- /vendor/golang.design/x/mainthread/mainthread.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-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 6 | 7 | // Package mainthread offers facilities to schedule functions 8 | // on the main thread. To use this package properly, one must 9 | // call `mainthread.Init` from the main package. For example: 10 | // 11 | // package main 12 | // 13 | // import "golang.design/x/mainthread" 14 | // 15 | // func main() { mainthread.Init(fn) } 16 | // 17 | // // fn is the actual main function 18 | // func fn() { 19 | // // ... do stuff ... 20 | // 21 | // // mainthread.Call returns when f1 returns. Note that if f1 blocks 22 | // // it will also block the execution of any subsequent calls on the 23 | // // main thread. 24 | // mainthread.Call(f1) 25 | // 26 | // // ... do stuff ... 27 | // 28 | // 29 | // // mainthread.Go returns immediately and f2 is scheduled to be 30 | // // executed in the future. 31 | // mainthread.Go(f2) 32 | // 33 | // // ... do stuff ... 34 | // } 35 | // 36 | // func f1() { ... } 37 | // func f2() { ... } 38 | // 39 | // If the given function triggers a panic, and called via `mainthread.Call`, 40 | // then the panic will be propagated to the same goroutine. One can capture 41 | // that panic, when possible: 42 | // 43 | // defer func() { 44 | // if r := recover(); r != nil { 45 | // println(r) 46 | // } 47 | // }() 48 | // 49 | // mainthread.Call(func() { ... }) // if panic 50 | // 51 | // If the given function triggers a panic, and called via `mainthread.Go`, 52 | // then the panic will be cached internally, until a call to the `Error()` method: 53 | // 54 | // mainthread.Go(func() { ... }) // if panics 55 | // 56 | // // ... do stuff ... 57 | // 58 | // if err := mainthread.Error(); err != nil { // can be captured here. 59 | // println(err) 60 | // } 61 | // 62 | // Note that a panic happens before `mainthread.Error()` returning the 63 | // panicked error. If one needs to guarantee `mainthread.Error()` indeed 64 | // captured the panic, a dummy function can be used as synchornization: 65 | // 66 | // mainthread.Go(func() { panic("die") }) // if panics 67 | // mainthread.Call(func() {}) // for execution synchronization 68 | // err := mainthread.Error() // err must be non-nil 69 | // 70 | // It is possible to cache up to a maximum of 42 panicked errors. 71 | // More errors are ignored. 72 | package mainthread // import "golang.design/x/mainthread" 73 | 74 | import ( 75 | "fmt" 76 | "runtime" 77 | "sync" 78 | ) 79 | 80 | func init() { 81 | runtime.LockOSThread() 82 | } 83 | 84 | // Init initializes the functionality of running arbitrary subsequent 85 | // functions be called on the main system thread. 86 | // 87 | // Init must be called in the main.main function. 88 | func Init(main func()) { 89 | done := donePool.Get().(chan error) 90 | defer donePool.Put(done) 91 | 92 | go func() { 93 | defer func() { 94 | done <- nil 95 | }() 96 | main() 97 | }() 98 | 99 | for { 100 | select { 101 | case f := <-funcQ: 102 | func() { 103 | defer func() { 104 | r := recover() 105 | if f.done != nil { 106 | if r != nil { 107 | f.done <- fmt.Errorf("%v", r) 108 | } else { 109 | f.done <- nil 110 | } 111 | } else { 112 | if r != nil { 113 | select { 114 | case erroQ <- fmt.Errorf("%v", r): 115 | default: 116 | } 117 | } 118 | } 119 | }() 120 | f.fn() 121 | }() 122 | case <-done: 123 | return 124 | } 125 | } 126 | } 127 | 128 | // Call calls f on the main thread and blocks until f finishes. 129 | func Call(f func()) { 130 | done := donePool.Get().(chan error) 131 | defer donePool.Put(done) 132 | 133 | data := funcData{fn: f, done: done} 134 | funcQ <- data 135 | if err := <-done; err != nil { 136 | panic(err) 137 | } 138 | } 139 | 140 | // Go schedules f to be called on the main thread. 141 | func Go(f func()) { 142 | funcQ <- funcData{fn: f} 143 | } 144 | 145 | // Error returns an error that is captured if there are any panics 146 | // happened on the mainthread. 147 | // 148 | // It is possible to cache up to a maximum of 42 panicked errors. 149 | // More errors are ignored. 150 | func Error() error { 151 | select { 152 | case err := <-erroQ: 153 | return err 154 | default: 155 | return nil 156 | } 157 | } 158 | 159 | var ( 160 | funcQ = make(chan funcData, runtime.GOMAXPROCS(0)) 161 | erroQ = make(chan error, 42) 162 | donePool = sync.Pool{New: func() interface{} { 163 | return make(chan error) 164 | }} 165 | ) 166 | 167 | type funcData struct { 168 | fn func() 169 | done chan error 170 | } 171 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/hotkey_linux.go: -------------------------------------------------------------------------------- 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 6 | 7 | //go:build linux 8 | 9 | package hotkey 10 | 11 | /* 12 | #cgo LDFLAGS: -lX11 13 | 14 | #include 15 | 16 | int displayTest(); 17 | int waitHotkey(uintptr_t hkhandle, unsigned int mod, int key); 18 | */ 19 | import "C" 20 | import ( 21 | "context" 22 | "errors" 23 | "runtime" 24 | "runtime/cgo" 25 | "sync" 26 | ) 27 | 28 | const errmsg = `Failed to initialize the X11 display, and the clipboard package 29 | will not work properly. Install the following dependency may help: 30 | 31 | apt install -y libx11-dev 32 | If the clipboard package is in an environment without a frame buffer, 33 | such as a cloud server, it may also be necessary to install xvfb: 34 | apt install -y xvfb 35 | and initialize a virtual frame buffer: 36 | Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 37 | export DISPLAY=:99.0 38 | Then this package should be ready to use. 39 | ` 40 | 41 | func init() { 42 | if C.displayTest() != 0 { 43 | panic(errmsg) 44 | } 45 | } 46 | 47 | type platformHotkey struct { 48 | mu sync.Mutex 49 | registered bool 50 | ctx context.Context 51 | cancel context.CancelFunc 52 | canceled chan struct{} 53 | } 54 | 55 | // Nothing needs to do for register 56 | func (hk *Hotkey) register() error { 57 | hk.mu.Lock() 58 | if hk.registered { 59 | hk.mu.Unlock() 60 | return errors.New("hotkey already registered.") 61 | } 62 | hk.registered = true 63 | hk.ctx, hk.cancel = context.WithCancel(context.Background()) 64 | hk.canceled = make(chan struct{}) 65 | hk.mu.Unlock() 66 | 67 | go hk.handle() 68 | return nil 69 | } 70 | 71 | // Nothing needs to do for unregister 72 | func (hk *Hotkey) unregister() error { 73 | hk.mu.Lock() 74 | defer hk.mu.Unlock() 75 | if !hk.registered { 76 | return errors.New("hotkey is not registered.") 77 | } 78 | hk.cancel() 79 | hk.registered = false 80 | <-hk.canceled 81 | return nil 82 | } 83 | 84 | // handle registers an application global hotkey to the system, 85 | // and returns a channel that will signal if the hotkey is triggered. 86 | func (hk *Hotkey) handle() { 87 | runtime.LockOSThread() 88 | defer runtime.UnlockOSThread() 89 | // KNOWN ISSUE: if a hotkey is grabbed by others, C side will crash the program 90 | 91 | var mod Modifier 92 | for _, m := range hk.mods { 93 | mod = mod | m 94 | } 95 | h := cgo.NewHandle(hk) 96 | defer h.Delete() 97 | 98 | for { 99 | select { 100 | case <-hk.ctx.Done(): 101 | close(hk.canceled) 102 | return 103 | default: 104 | _ = C.waitHotkey(C.uintptr_t(h), C.uint(mod), C.int(hk.key)) 105 | } 106 | } 107 | } 108 | 109 | //export hotkeyDown 110 | func hotkeyDown(h uintptr) { 111 | hk := cgo.Handle(h).Value().(*Hotkey) 112 | hk.keydownIn <- Event{} 113 | } 114 | 115 | //export hotkeyUp 116 | func hotkeyUp(h uintptr) { 117 | hk := cgo.Handle(h).Value().(*Hotkey) 118 | hk.keyupIn <- Event{} 119 | } 120 | 121 | // Modifier represents a modifier. 122 | type Modifier uint32 123 | 124 | // All kinds of Modifiers 125 | // See /usr/include/X11/X.h 126 | const ( 127 | ModCtrl Modifier = (1 << 2) 128 | ModShift Modifier = (1 << 0) 129 | Mod1 Modifier = (1 << 3) 130 | Mod2 Modifier = (1 << 4) 131 | Mod3 Modifier = (1 << 5) 132 | Mod4 Modifier = (1 << 6) 133 | Mod5 Modifier = (1 << 7) 134 | ) 135 | 136 | // Key represents a key. 137 | // See /usr/include/X11/keysymdef.h 138 | type Key uint16 139 | 140 | // All kinds of keys 141 | const ( 142 | KeySpace Key = 0x0020 143 | Key1 Key = 0x0030 144 | Key2 Key = 0x0031 145 | Key3 Key = 0x0032 146 | Key4 Key = 0x0033 147 | Key5 Key = 0x0034 148 | Key6 Key = 0x0035 149 | Key7 Key = 0x0036 150 | Key8 Key = 0x0037 151 | Key9 Key = 0x0038 152 | Key0 Key = 0x0039 153 | KeyA Key = 0x0061 154 | KeyB Key = 0x0062 155 | KeyC Key = 0x0063 156 | KeyD Key = 0x0064 157 | KeyE Key = 0x0065 158 | KeyF Key = 0x0066 159 | KeyG Key = 0x0067 160 | KeyH Key = 0x0068 161 | KeyI Key = 0x0069 162 | KeyJ Key = 0x006a 163 | KeyK Key = 0x006b 164 | KeyL Key = 0x006c 165 | KeyM Key = 0x006d 166 | KeyN Key = 0x006e 167 | KeyO Key = 0x006f 168 | KeyP Key = 0x0070 169 | KeyQ Key = 0x0071 170 | KeyR Key = 0x0072 171 | KeyS Key = 0x0073 172 | KeyT Key = 0x0074 173 | KeyU Key = 0x0075 174 | KeyV Key = 0x0076 175 | KeyW Key = 0x0077 176 | KeyX Key = 0x0078 177 | KeyY Key = 0x0079 178 | KeyZ Key = 0x007a 179 | 180 | KeyReturn Key = 0xff0d 181 | KeyEscape Key = 0xff1b 182 | KeyDelete Key = 0xffff 183 | KeyTab Key = 0xff1b 184 | 185 | KeyLeft Key = 0xff51 186 | KeyRight Key = 0xff53 187 | KeyUp Key = 0xff52 188 | KeyDown Key = 0xff54 189 | 190 | KeyF1 Key = 0xffbe 191 | KeyF2 Key = 0xffbf 192 | KeyF3 Key = 0xffc0 193 | KeyF4 Key = 0xffc1 194 | KeyF5 Key = 0xffc2 195 | KeyF6 Key = 0xffc3 196 | KeyF7 Key = 0xffc4 197 | KeyF8 Key = 0xffc5 198 | KeyF9 Key = 0xffc6 199 | KeyF10 Key = 0xffc7 200 | KeyF11 Key = 0xffc8 201 | KeyF12 Key = 0xffc9 202 | KeyF13 Key = 0xffca 203 | KeyF14 Key = 0xffcb 204 | KeyF15 Key = 0xffcc 205 | KeyF16 Key = 0xffcd 206 | KeyF17 Key = 0xffce 207 | KeyF18 Key = 0xffcf 208 | KeyF19 Key = 0xffd0 209 | KeyF20 Key = 0xffd1 210 | ) 211 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/hotkey_windows.go: -------------------------------------------------------------------------------- 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 6 | 7 | //go:build windows 8 | 9 | package hotkey 10 | 11 | import ( 12 | "errors" 13 | "runtime" 14 | "sync" 15 | "sync/atomic" 16 | "time" 17 | 18 | "golang.design/x/hotkey/internal/win" 19 | ) 20 | 21 | type platformHotkey struct { 22 | mu sync.Mutex 23 | hotkeyId uint64 24 | registered bool 25 | funcs chan func() 26 | canceled chan struct{} 27 | } 28 | 29 | var hotkeyId uint64 // atomic 30 | 31 | // register registers a system hotkey. It returns an error if 32 | // the registration is failed. This could be that the hotkey is 33 | // conflict with other hotkeys. 34 | func (hk *Hotkey) register() error { 35 | hk.mu.Lock() 36 | if hk.registered { 37 | hk.mu.Unlock() 38 | return errors.New("hotkey already registered") 39 | } 40 | 41 | mod := uint8(0) 42 | for _, m := range hk.mods { 43 | mod = mod | uint8(m) 44 | } 45 | 46 | hk.hotkeyId = atomic.AddUint64(&hotkeyId, 1) 47 | hk.funcs = make(chan func()) 48 | hk.canceled = make(chan struct{}) 49 | go hk.handle() 50 | 51 | var ( 52 | ok bool 53 | err error 54 | done = make(chan struct{}) 55 | ) 56 | hk.funcs <- func() { 57 | ok, err = win.RegisterHotKey(0, uintptr(hk.hotkeyId), uintptr(mod), uintptr(hk.key)) 58 | done <- struct{}{} 59 | } 60 | <-done 61 | if !ok { 62 | close(hk.canceled) 63 | hk.mu.Unlock() 64 | return err 65 | } 66 | hk.registered = true 67 | hk.mu.Unlock() 68 | return nil 69 | } 70 | 71 | // unregister deregisteres a system hotkey. 72 | func (hk *Hotkey) unregister() error { 73 | hk.mu.Lock() 74 | defer hk.mu.Unlock() 75 | if !hk.registered { 76 | return errors.New("hotkey is not registered") 77 | } 78 | 79 | done := make(chan struct{}) 80 | hk.funcs <- func() { 81 | win.UnregisterHotKey(0, uintptr(hk.hotkeyId)) 82 | done <- struct{}{} 83 | close(hk.canceled) 84 | } 85 | <-done 86 | 87 | <-hk.canceled 88 | hk.registered = false 89 | return nil 90 | } 91 | 92 | const ( 93 | // wmHotkey represents hotkey message 94 | wmHotkey uint32 = 0x0312 95 | wmQuit uint32 = 0x0012 96 | ) 97 | 98 | // handle handles the hotkey event loop. 99 | func (hk *Hotkey) handle() { 100 | // We could optimize this. So far each hotkey is served in an 101 | // individual thread. If we have too many hotkeys, then a program 102 | // have to create too many threads to serve them. 103 | runtime.LockOSThread() 104 | defer runtime.UnlockOSThread() 105 | 106 | tk := time.NewTicker(time.Second / 100) 107 | for range tk.C { 108 | msg := win.MSG{} 109 | if !win.PeekMessage(&msg, 0, 0, 0) { 110 | select { 111 | case f := <-hk.funcs: 112 | f() 113 | case <-hk.canceled: 114 | return 115 | default: 116 | } 117 | continue 118 | } 119 | if !win.GetMessage(&msg, 0, 0, 0) { 120 | return 121 | } 122 | 123 | switch msg.Message { 124 | case wmHotkey: 125 | hk.keydownIn <- Event{} 126 | 127 | tk := time.NewTicker(time.Second / 100) 128 | for range tk.C { 129 | if win.GetAsyncKeyState(int(hk.key)) == 0 { 130 | hk.keyupIn <- Event{} 131 | break 132 | } 133 | } 134 | case wmQuit: 135 | return 136 | } 137 | } 138 | } 139 | 140 | // Modifier represents a modifier. 141 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey 142 | type Modifier uint8 143 | 144 | // All kinds of Modifiers 145 | const ( 146 | ModAlt Modifier = 0x1 147 | ModCtrl Modifier = 0x2 148 | ModShift Modifier = 0x4 149 | ModWin Modifier = 0x8 150 | ) 151 | 152 | // Key represents a key. 153 | // https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes 154 | type Key uint16 155 | 156 | // All kinds of Keys 157 | const ( 158 | KeySpace Key = 0x20 159 | Key0 Key = 0x30 160 | Key1 Key = 0x31 161 | Key2 Key = 0x32 162 | Key3 Key = 0x33 163 | Key4 Key = 0x34 164 | Key5 Key = 0x35 165 | Key6 Key = 0x36 166 | Key7 Key = 0x37 167 | Key8 Key = 0x38 168 | Key9 Key = 0x39 169 | KeyA Key = 0x41 170 | KeyB Key = 0x42 171 | KeyC Key = 0x43 172 | KeyD Key = 0x44 173 | KeyE Key = 0x45 174 | KeyF Key = 0x46 175 | KeyG Key = 0x47 176 | KeyH Key = 0x48 177 | KeyI Key = 0x49 178 | KeyJ Key = 0x4A 179 | KeyK Key = 0x4B 180 | KeyL Key = 0x4C 181 | KeyM Key = 0x4D 182 | KeyN Key = 0x4E 183 | KeyO Key = 0x4F 184 | KeyP Key = 0x50 185 | KeyQ Key = 0x51 186 | KeyR Key = 0x52 187 | KeyS Key = 0x53 188 | KeyT Key = 0x54 189 | KeyU Key = 0x55 190 | KeyV Key = 0x56 191 | KeyW Key = 0x57 192 | KeyX Key = 0x58 193 | KeyY Key = 0x59 194 | KeyZ Key = 0x5A 195 | 196 | KeyReturn Key = 0x0D 197 | KeyEscape Key = 0x1B 198 | KeyDelete Key = 0x2E 199 | KeyTab Key = 0x09 200 | 201 | KeyLeft Key = 0x25 202 | KeyRight Key = 0x27 203 | KeyUp Key = 0x26 204 | KeyDown Key = 0x28 205 | 206 | KeyF1 Key = 0x70 207 | KeyF2 Key = 0x71 208 | KeyF3 Key = 0x72 209 | KeyF4 Key = 0x73 210 | KeyF5 Key = 0x74 211 | KeyF6 Key = 0x75 212 | KeyF7 Key = 0x76 213 | KeyF8 Key = 0x77 214 | KeyF9 Key = 0x78 215 | KeyF10 Key = 0x79 216 | KeyF11 Key = 0x7A 217 | KeyF12 Key = 0x7B 218 | KeyF13 Key = 0x7C 219 | KeyF14 Key = 0x7D 220 | KeyF15 Key = 0x7E 221 | KeyF16 Key = 0x7F 222 | KeyF17 Key = 0x80 223 | KeyF18 Key = 0x81 224 | KeyF19 Key = 0x82 225 | KeyF20 Key = 0x83 226 | ) 227 | -------------------------------------------------------------------------------- /vendor/golang.design/x/hotkey/hotkey.go: -------------------------------------------------------------------------------- 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 6 | 7 | // Package hotkey provides the basic facility to register a system-level 8 | // global hotkey shortcut so that an application can be notified if a user 9 | // triggers the desired hotkey. A hotkey must be a combination of modifiers 10 | // and a single key. 11 | // 12 | // Note platform specific details: 13 | // 14 | // - On macOS, due to the OS restriction (other platforms does not have 15 | // this restriction), hotkey events must be handled on the "main thread". 16 | // Therefore, in order to use this package properly, one must start an 17 | // OS main event loop on the main thread, For self-contained applications, 18 | // using [mainthread] package. 19 | // is possible. It is uncessary or applications based on other GUI frameworks, 20 | // such as fyne, ebiten, or Gio. See the "[examples]" for more examples. 21 | // 22 | // - On Linux (X11), when AutoRepeat is enabled in the X server, the 23 | // Keyup is triggered automatically and continuously as Keydown continues. 24 | // 25 | // - On Linux (X11), some keys may be mapped to multiple Mod keys. To 26 | // correctly register the key combination, one must use the correct 27 | // underlying keycode combination. For example, a regular Ctrl+Alt+S 28 | // might be registered as: Ctrl+Mod2+Mod4+S. 29 | // 30 | // - If this package did not include a desired key, one can always provide 31 | // the keycode to the API. For example, if a key code is 0x15, then the 32 | // corresponding key is `hotkey.Key(0x15)`. 33 | // 34 | // THe following is a minimum example: 35 | // 36 | // package main 37 | // 38 | // import ( 39 | // "log" 40 | // 41 | // "golang.design/x/hotkey" 42 | // "golang.design/x/hotkey/mainthread" 43 | // ) 44 | // 45 | // func main() { mainthread.Init(fn) } // Not necessary when use in Fyne, Ebiten or Gio. 46 | // func fn() { 47 | // hk := hotkey.New([]hotkey.Modifier{hotkey.ModCtrl, hotkey.ModShift}, hotkey.KeyS) 48 | // err := hk.Register() 49 | // if err != nil { 50 | // log.Fatalf("hotkey: failed to register hotkey: %v", err) 51 | // } 52 | // 53 | // log.Printf("hotkey: %v is registered\n", hk) 54 | // <-hk.Keydown() 55 | // log.Printf("hotkey: %v is down\n", hk) 56 | // <-hk.Keyup() 57 | // log.Printf("hotkey: %v is up\n", hk) 58 | // hk.Unregister() 59 | // log.Printf("hotkey: %v is unregistered\n", hk) 60 | // } 61 | // 62 | // [mainthread]: https://pkg.go.dev/golang.design/x/hotkey/mainthread 63 | // [examples]: https://github.com/golang-design/hotkey/tree/main/examples 64 | package hotkey 65 | 66 | import ( 67 | "fmt" 68 | "runtime" 69 | ) 70 | 71 | // Event represents a hotkey event 72 | type Event struct{} 73 | 74 | // Hotkey is a combination of modifiers and key to trigger an event 75 | type Hotkey struct { 76 | platformHotkey 77 | 78 | mods []Modifier 79 | key Key 80 | 81 | keydownIn chan<- Event 82 | keydownOut <-chan Event 83 | keyupIn chan<- Event 84 | keyupOut <-chan Event 85 | } 86 | 87 | // New creates a new hotkey for the given modifiers and keycode. 88 | func New(mods []Modifier, key Key) *Hotkey { 89 | keydownIn, keydownOut := newEventChan() 90 | keyupIn, keyupOut := newEventChan() 91 | hk := &Hotkey{ 92 | mods: mods, 93 | key: key, 94 | keydownIn: keydownIn, 95 | keydownOut: keydownOut, 96 | keyupIn: keyupIn, 97 | keyupOut: keyupOut, 98 | } 99 | 100 | // Make sure the hotkey is unregistered when the created 101 | // hotkey is garbage collected. 102 | runtime.SetFinalizer(hk, func(x interface{}) { 103 | hk := x.(*Hotkey) 104 | hk.unregister() 105 | close(hk.keydownIn) 106 | close(hk.keyupIn) 107 | }) 108 | return hk 109 | } 110 | 111 | // Register registers a combination of hotkeys. If the hotkey has 112 | // registered. This function will invalidates the old registration 113 | // and overwrites its callback. 114 | func (hk *Hotkey) Register() error { return hk.register() } 115 | 116 | // Keydown returns a channel that receives a signal when the hotkey is triggered. 117 | func (hk *Hotkey) Keydown() <-chan Event { return hk.keydownOut } 118 | 119 | // Keyup returns a channel that receives a signal when the hotkey is released. 120 | func (hk *Hotkey) Keyup() <-chan Event { return hk.keyupOut } 121 | 122 | // Unregister unregisters the hotkey. 123 | func (hk *Hotkey) Unregister() error { 124 | err := hk.unregister() 125 | if err != nil { 126 | return err 127 | } 128 | 129 | // Reset a new event channel. 130 | close(hk.keydownIn) 131 | close(hk.keyupIn) 132 | hk.keydownIn, hk.keydownOut = newEventChan() 133 | hk.keyupIn, hk.keyupOut = newEventChan() 134 | return nil 135 | } 136 | 137 | // String returns a string representation of the hotkey. 138 | func (hk *Hotkey) String() string { 139 | s := fmt.Sprintf("%v", hk.key) 140 | for _, mod := range hk.mods { 141 | s += fmt.Sprintf("+%v", mod) 142 | } 143 | return s 144 | } 145 | 146 | // newEventChan returns a sender and a receiver of a buffered channel 147 | // with infinite capacity. 148 | func newEventChan() (chan<- Event, <-chan Event) { 149 | in, out := make(chan Event), make(chan Event) 150 | 151 | go func() { 152 | var q []Event 153 | 154 | for { 155 | e, ok := <-in 156 | if !ok { 157 | close(out) 158 | return 159 | } 160 | q = append(q, e) 161 | for len(q) > 0 { 162 | select { 163 | case out <- q[0]: 164 | q[0] = Event{} 165 | q = q[1:] 166 | case e, ok := <-in: 167 | if ok { 168 | q = append(q, e) 169 | break 170 | } 171 | for _, e := range q { 172 | out <- e 173 | } 174 | close(out) 175 | return 176 | } 177 | } 178 | } 179 | }() 180 | return in, out 181 | } 182 | --------------------------------------------------------------------------------