⚡ Optimize hotkey service
This commit is contained in:
@@ -1,210 +1,156 @@
|
||||
// Copyright 2021 The golang.design Initiative Authors.
|
||||
// All rights reserved. Use of this source code is governed
|
||||
// by a MIT license that can be found in the LICENSE file.
|
||||
//
|
||||
// Written by Changkun Ou <changkun.de>
|
||||
|
||||
//go:build linux
|
||||
|
||||
package hotkey
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lX11
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int displayTest();
|
||||
int waitHotkey(uintptr_t hkhandle, unsigned int mod, int key);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const errmsg = `Failed to initialize the X11 display, and the clipboard package
|
||||
will not work properly. Install the following dependency may help:
|
||||
|
||||
apt install -y libx11-dev
|
||||
If the clipboard package is in an environment without a frame buffer,
|
||||
such as a cloud server, it may also be necessary to install xvfb:
|
||||
apt install -y xvfb
|
||||
and initialize a virtual frame buffer:
|
||||
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
|
||||
export DISPLAY=:99.0
|
||||
Then this package should be ready to use.
|
||||
`
|
||||
|
||||
func init() {
|
||||
if C.displayTest() != 0 {
|
||||
panic(errmsg)
|
||||
}
|
||||
}
|
||||
import "voidraft/internal/common/hotkey/linux"
|
||||
|
||||
type platformHotkey struct {
|
||||
mu sync.Mutex
|
||||
registered bool
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
canceled chan struct{}
|
||||
}
|
||||
|
||||
// Nothing needs to do for register
|
||||
func (hk *Hotkey) register() error {
|
||||
hk.mu.Lock()
|
||||
if hk.registered {
|
||||
hk.mu.Unlock()
|
||||
return errors.New("hotkey already registered.")
|
||||
}
|
||||
hk.registered = true
|
||||
hk.ctx, hk.cancel = context.WithCancel(context.Background())
|
||||
hk.canceled = make(chan struct{})
|
||||
hk.mu.Unlock()
|
||||
|
||||
go hk.handle()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Nothing needs to do for unregister
|
||||
func (hk *Hotkey) unregister() error {
|
||||
hk.mu.Lock()
|
||||
defer hk.mu.Unlock()
|
||||
if !hk.registered {
|
||||
return errors.New("hotkey is not registered.")
|
||||
}
|
||||
hk.cancel()
|
||||
hk.registered = false
|
||||
<-hk.canceled
|
||||
return nil
|
||||
}
|
||||
|
||||
// handle registers an application global hotkey to the system,
|
||||
// and returns a channel that will signal if the hotkey is triggered.
|
||||
func (hk *Hotkey) handle() {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
// KNOWN ISSUE: if a hotkey is grabbed by others, C side will crash the program
|
||||
|
||||
var mod Modifier
|
||||
for _, m := range hk.mods {
|
||||
mod = mod | m
|
||||
}
|
||||
h := cgo.NewHandle(hk)
|
||||
defer h.Delete()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-hk.ctx.Done():
|
||||
close(hk.canceled)
|
||||
return
|
||||
default:
|
||||
_ = C.waitHotkey(C.uintptr_t(h), C.uint(mod), C.int(hk.key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export hotkeyDown
|
||||
func hotkeyDown(h uintptr) {
|
||||
hk := cgo.Handle(h).Value().(*Hotkey)
|
||||
hk.keydownIn <- Event{}
|
||||
}
|
||||
|
||||
//export hotkeyUp
|
||||
func hotkeyUp(h uintptr) {
|
||||
hk := cgo.Handle(h).Value().(*Hotkey)
|
||||
hk.keyupIn <- Event{}
|
||||
ph linux.PlatformHotkey
|
||||
keydownIn chan interface{}
|
||||
keyupIn chan interface{}
|
||||
stopChans chan struct{} // 用于停止通道转换 goroutines
|
||||
}
|
||||
|
||||
// Modifier represents a modifier.
|
||||
type Modifier uint32
|
||||
type Modifier = linux.Modifier
|
||||
|
||||
// All kinds of Modifiers
|
||||
// See /usr/include/X11/X.h
|
||||
const (
|
||||
ModCtrl Modifier = (1 << 2)
|
||||
ModShift Modifier = (1 << 0)
|
||||
Mod1 Modifier = (1 << 3)
|
||||
Mod2 Modifier = (1 << 4)
|
||||
Mod3 Modifier = (1 << 5)
|
||||
Mod4 Modifier = (1 << 6)
|
||||
Mod5 Modifier = (1 << 7)
|
||||
ModCtrl = linux.ModCtrl
|
||||
ModShift = linux.ModShift
|
||||
ModAlt = linux.ModAlt // Alias for Mod1
|
||||
Mod1 = linux.Mod1
|
||||
Mod2 = linux.Mod2
|
||||
Mod3 = linux.Mod3
|
||||
Mod4 = linux.Mod4
|
||||
Mod5 = linux.Mod5
|
||||
)
|
||||
|
||||
// Key represents a key.
|
||||
// See /usr/include/X11/keysymdef.h
|
||||
type Key uint16
|
||||
type Key = linux.Key
|
||||
|
||||
// All kinds of keys
|
||||
const (
|
||||
KeySpace Key = 0x0020
|
||||
Key1 Key = 0x0030
|
||||
Key2 Key = 0x0031
|
||||
Key3 Key = 0x0032
|
||||
Key4 Key = 0x0033
|
||||
Key5 Key = 0x0034
|
||||
Key6 Key = 0x0035
|
||||
Key7 Key = 0x0036
|
||||
Key8 Key = 0x0037
|
||||
Key9 Key = 0x0038
|
||||
Key0 Key = 0x0039
|
||||
KeyA Key = 0x0061
|
||||
KeyB Key = 0x0062
|
||||
KeyC Key = 0x0063
|
||||
KeyD Key = 0x0064
|
||||
KeyE Key = 0x0065
|
||||
KeyF Key = 0x0066
|
||||
KeyG Key = 0x0067
|
||||
KeyH Key = 0x0068
|
||||
KeyI Key = 0x0069
|
||||
KeyJ Key = 0x006a
|
||||
KeyK Key = 0x006b
|
||||
KeyL Key = 0x006c
|
||||
KeyM Key = 0x006d
|
||||
KeyN Key = 0x006e
|
||||
KeyO Key = 0x006f
|
||||
KeyP Key = 0x0070
|
||||
KeyQ Key = 0x0071
|
||||
KeyR Key = 0x0072
|
||||
KeyS Key = 0x0073
|
||||
KeyT Key = 0x0074
|
||||
KeyU Key = 0x0075
|
||||
KeyV Key = 0x0076
|
||||
KeyW Key = 0x0077
|
||||
KeyX Key = 0x0078
|
||||
KeyY Key = 0x0079
|
||||
KeyZ Key = 0x007a
|
||||
KeySpace = linux.KeySpace
|
||||
Key1 = linux.Key1
|
||||
Key2 = linux.Key2
|
||||
Key3 = linux.Key3
|
||||
Key4 = linux.Key4
|
||||
Key5 = linux.Key5
|
||||
Key6 = linux.Key6
|
||||
Key7 = linux.Key7
|
||||
Key8 = linux.Key8
|
||||
Key9 = linux.Key9
|
||||
Key0 = linux.Key0
|
||||
KeyA = linux.KeyA
|
||||
KeyB = linux.KeyB
|
||||
KeyC = linux.KeyC
|
||||
KeyD = linux.KeyD
|
||||
KeyE = linux.KeyE
|
||||
KeyF = linux.KeyF
|
||||
KeyG = linux.KeyG
|
||||
KeyH = linux.KeyH
|
||||
KeyI = linux.KeyI
|
||||
KeyJ = linux.KeyJ
|
||||
KeyK = linux.KeyK
|
||||
KeyL = linux.KeyL
|
||||
KeyM = linux.KeyM
|
||||
KeyN = linux.KeyN
|
||||
KeyO = linux.KeyO
|
||||
KeyP = linux.KeyP
|
||||
KeyQ = linux.KeyQ
|
||||
KeyR = linux.KeyR
|
||||
KeyS = linux.KeyS
|
||||
KeyT = linux.KeyT
|
||||
KeyU = linux.KeyU
|
||||
KeyV = linux.KeyV
|
||||
KeyW = linux.KeyW
|
||||
KeyX = linux.KeyX
|
||||
KeyY = linux.KeyY
|
||||
KeyZ = linux.KeyZ
|
||||
|
||||
KeyReturn Key = 0xff0d
|
||||
KeyEscape Key = 0xff1b
|
||||
KeyDelete Key = 0xffff
|
||||
KeyTab Key = 0xff1b
|
||||
KeyReturn = linux.KeyReturn
|
||||
KeyEscape = linux.KeyEscape
|
||||
KeyDelete = linux.KeyDelete
|
||||
KeyTab = linux.KeyTab
|
||||
|
||||
KeyLeft Key = 0xff51
|
||||
KeyRight Key = 0xff53
|
||||
KeyUp Key = 0xff52
|
||||
KeyDown Key = 0xff54
|
||||
KeyLeft = linux.KeyLeft
|
||||
KeyRight = linux.KeyRight
|
||||
KeyUp = linux.KeyUp
|
||||
KeyDown = linux.KeyDown
|
||||
|
||||
KeyF1 Key = 0xffbe
|
||||
KeyF2 Key = 0xffbf
|
||||
KeyF3 Key = 0xffc0
|
||||
KeyF4 Key = 0xffc1
|
||||
KeyF5 Key = 0xffc2
|
||||
KeyF6 Key = 0xffc3
|
||||
KeyF7 Key = 0xffc4
|
||||
KeyF8 Key = 0xffc5
|
||||
KeyF9 Key = 0xffc6
|
||||
KeyF10 Key = 0xffc7
|
||||
KeyF11 Key = 0xffc8
|
||||
KeyF12 Key = 0xffc9
|
||||
KeyF13 Key = 0xffca
|
||||
KeyF14 Key = 0xffcb
|
||||
KeyF15 Key = 0xffcc
|
||||
KeyF16 Key = 0xffcd
|
||||
KeyF17 Key = 0xffce
|
||||
KeyF18 Key = 0xffcf
|
||||
KeyF19 Key = 0xffd0
|
||||
KeyF20 Key = 0xffd1
|
||||
KeyF1 = linux.KeyF1
|
||||
KeyF2 = linux.KeyF2
|
||||
KeyF3 = linux.KeyF3
|
||||
KeyF4 = linux.KeyF4
|
||||
KeyF5 = linux.KeyF5
|
||||
KeyF6 = linux.KeyF6
|
||||
KeyF7 = linux.KeyF7
|
||||
KeyF8 = linux.KeyF8
|
||||
KeyF9 = linux.KeyF9
|
||||
KeyF10 = linux.KeyF10
|
||||
KeyF11 = linux.KeyF11
|
||||
KeyF12 = linux.KeyF12
|
||||
KeyF13 = linux.KeyF13
|
||||
KeyF14 = linux.KeyF14
|
||||
KeyF15 = linux.KeyF15
|
||||
KeyF16 = linux.KeyF16
|
||||
KeyF17 = linux.KeyF17
|
||||
KeyF18 = linux.KeyF18
|
||||
KeyF19 = linux.KeyF19
|
||||
KeyF20 = linux.KeyF20
|
||||
)
|
||||
|
||||
func (hk *Hotkey) register() error {
|
||||
// Convert channels
|
||||
hk.platformHotkey.keydownIn = make(chan interface{}, 1)
|
||||
hk.platformHotkey.keyupIn = make(chan interface{}, 1)
|
||||
hk.platformHotkey.stopChans = make(chan struct{})
|
||||
|
||||
// Start goroutines to convert interface{} events to Event{}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-hk.platformHotkey.stopChans:
|
||||
return
|
||||
case <-hk.platformHotkey.keydownIn:
|
||||
hk.keydownIn <- Event{}
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-hk.platformHotkey.stopChans:
|
||||
return
|
||||
case <-hk.platformHotkey.keyupIn:
|
||||
hk.keyupIn <- Event{}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return hk.platformHotkey.ph.Register(hk.mods, hk.key, hk.platformHotkey.keydownIn, hk.platformHotkey.keyupIn)
|
||||
}
|
||||
|
||||
func (hk *Hotkey) unregister() error {
|
||||
// Stop channel conversion goroutines first
|
||||
if hk.platformHotkey.stopChans != nil {
|
||||
select {
|
||||
case <-hk.platformHotkey.stopChans:
|
||||
// Already closed, do nothing
|
||||
default:
|
||||
close(hk.platformHotkey.stopChans)
|
||||
}
|
||||
hk.platformHotkey.stopChans = nil
|
||||
}
|
||||
|
||||
// Then unregister the hotkey
|
||||
err := hk.platformHotkey.ph.Unregister()
|
||||
|
||||
// Close conversion channels (don't close, just set to nil)
|
||||
// The goroutines will drain them when stopChans is closed
|
||||
hk.platformHotkey.keydownIn = nil
|
||||
hk.platformHotkey.keyupIn = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user