Optimize hotkey service

This commit is contained in:
2025-11-06 22:42:44 +08:00
parent e0179b5838
commit 551e7e2cfd
26 changed files with 2917 additions and 1116 deletions

View File

@@ -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
}