1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-08 06:25:37 -05:00

core: improve evdev capslock detection, wayland context fixes

This commit is contained in:
bbedward
2025-11-14 12:04:08 -05:00
parent 72534b7674
commit 5685e39631
17 changed files with 299 additions and 93 deletions

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/fsnotify/fsnotify"
evdev "github.com/holoplot/go-evdev"
)
@@ -29,31 +30,57 @@ type EvdevDevice interface {
}
type Manager struct {
device EvdevDevice
state State
stateMutex sync.RWMutex
subscribers map[string]chan State
subMutex sync.RWMutex
closeChan chan struct{}
closeOnce sync.Once
devices []EvdevDevice
devicesMutex sync.RWMutex
monitoredPaths map[string]bool
state State
stateMutex sync.RWMutex
subscribers map[string]chan State
subMutex sync.RWMutex
closeChan chan struct{}
closeOnce sync.Once
watcher *fsnotify.Watcher
}
func NewManager() (*Manager, error) {
device, err := findKeyboard()
devices, err := findKeyboards()
if err != nil {
return nil, fmt.Errorf("failed to find keyboard: %w", err)
return nil, fmt.Errorf("failed to find keyboards: %w", err)
}
initialCapsLock := readInitialCapsLockState(device)
initialCapsLock := readInitialCapsLockState(devices[0])
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Warnf("Failed to create fsnotify watcher, hotplug detection disabled: %v", err)
watcher = nil
} else if err := watcher.Add("/dev/input"); err != nil {
log.Warnf("Failed to watch /dev/input, hotplug detection disabled: %v", err)
watcher.Close()
watcher = nil
}
monitoredPaths := make(map[string]bool)
for _, device := range devices {
monitoredPaths[device.Path()] = true
}
m := &Manager{
device: device,
state: State{Available: true, CapsLock: initialCapsLock},
subscribers: make(map[string]chan State),
closeChan: make(chan struct{}),
devices: devices,
monitoredPaths: monitoredPaths,
state: State{Available: true, CapsLock: initialCapsLock},
subscribers: make(map[string]chan State),
closeChan: make(chan struct{}),
watcher: watcher,
}
go m.monitorCapsLock()
for i, device := range devices {
go m.monitorDevice(device, i)
}
if watcher != nil {
go m.watchForNewKeyboards()
}
return m, nil
}
@@ -68,7 +95,7 @@ func readInitialCapsLockState(device EvdevDevice) bool {
return ledStates[ledCapslockKey]
}
func findKeyboard() (EvdevDevice, error) {
func findKeyboards() ([]EvdevDevice, error) {
pattern := "/dev/input/event*"
matches, err := filepath.Glob(pattern)
if err != nil {
@@ -79,22 +106,28 @@ func findKeyboard() (EvdevDevice, error) {
return nil, fmt.Errorf("no input devices found")
}
var keyboards []EvdevDevice
for _, path := range matches {
device, err := evdev.Open(path)
if err != nil {
continue
}
if isKeyboard(device) {
deviceName, _ := device.Name()
log.Debugf("Found keyboard: %s at %s", deviceName, path)
return device, nil
if !isKeyboard(device) {
device.Close()
continue
}
device.Close()
deviceName, _ := device.Name()
log.Debugf("Found keyboard: %s at %s", deviceName, path)
keyboards = append(keyboards, device)
}
return nil, fmt.Errorf("no keyboard device found")
if len(keyboards) == 0 {
return nil, fmt.Errorf("no keyboard device found")
}
return keyboards, nil
}
func isKeyboard(device EvdevDevice) bool {
@@ -117,7 +150,85 @@ func isKeyboard(device EvdevDevice) bool {
}
}
func (m *Manager) monitorCapsLock() {
func (m *Manager) watchForNewKeyboards() {
defer func() {
if r := recover(); r != nil {
log.Errorf("Panic in keyboard hotplug monitor: %v", r)
}
}()
for {
select {
case <-m.closeChan:
return
case event, ok := <-m.watcher.Events:
if !ok {
return
}
if !strings.HasPrefix(filepath.Base(event.Name), "event") {
continue
}
if event.Op&fsnotify.Create == fsnotify.Create {
time.Sleep(100 * time.Millisecond)
m.devicesMutex.Lock()
if m.monitoredPaths[event.Name] {
m.devicesMutex.Unlock()
continue
}
device, err := evdev.Open(event.Name)
if err != nil {
m.devicesMutex.Unlock()
continue
}
if !isKeyboard(device) {
device.Close()
m.devicesMutex.Unlock()
continue
}
deviceName, _ := device.Name()
log.Debugf("Hotplugged keyboard: %s at %s", deviceName, event.Name)
m.devices = append(m.devices, device)
m.monitoredPaths[event.Name] = true
deviceIndex := len(m.devices) - 1
m.devicesMutex.Unlock()
go m.monitorDevice(device, deviceIndex)
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
m.devicesMutex.Lock()
if !m.monitoredPaths[event.Name] {
m.devicesMutex.Unlock()
continue
}
delete(m.monitoredPaths, event.Name)
for i, device := range m.devices {
if device != nil && device.Path() == event.Name {
log.Debugf("Keyboard removed: %s", event.Name)
device.Close()
m.devices[i] = nil
break
}
}
m.devicesMutex.Unlock()
}
case err, ok := <-m.watcher.Errors:
if !ok {
return
}
log.Warnf("Keyboard hotplug watcher error: %v", err)
}
}
}
func (m *Manager) monitorDevice(device EvdevDevice, deviceIndex int) {
defer func() {
if r := recover(); r != nil {
log.Errorf("Panic in evdev monitor: %v", r)
@@ -131,11 +242,12 @@ func (m *Manager) monitorCapsLock() {
default:
}
event, err := m.device.ReadOne()
event, err := device.ReadOne()
if err != nil {
if !isClosedError(err) {
log.Warnf("Failed to read evdev event: %v", err)
if isClosedError(err) {
return
}
log.Warnf("Failed to read evdev event: %v", err)
time.Sleep(100 * time.Millisecond)
continue
}
@@ -145,7 +257,11 @@ func (m *Manager) monitorCapsLock() {
}
if event.Type == evKeyType && event.Code == keyCapslockKey && event.Value == keyStateOn {
m.toggleCapsLock()
time.Sleep(50 * time.Millisecond)
m.readAndUpdateCapsLockState(deviceIndex)
} else if event.Type == evLedType && event.Code == ledCapslockKey {
capsLockState := event.Value == keyStateOn
m.updateCapsLockStateDirect(capsLockState)
}
}
}
@@ -166,13 +282,37 @@ func isClosedError(err error) bool {
}
}
func (m *Manager) toggleCapsLock() {
func (m *Manager) readAndUpdateCapsLockState(deviceIndex int) {
m.devicesMutex.RLock()
if deviceIndex >= len(m.devices) {
m.devicesMutex.RUnlock()
return
}
device := m.devices[deviceIndex]
m.devicesMutex.RUnlock()
ledStates, err := device.State(evLedType)
if err != nil {
log.Warnf("Failed to read LED state: %v", err)
return
}
capsLockState := ledStates[ledCapslockKey]
m.updateCapsLockStateDirect(capsLockState)
}
func (m *Manager) updateCapsLockStateDirect(capsLockState bool) {
m.stateMutex.Lock()
m.state.CapsLock = !m.state.CapsLock
if m.state.CapsLock == capsLockState {
m.stateMutex.Unlock()
return
}
m.state.CapsLock = capsLockState
newState := m.state
m.stateMutex.Unlock()
log.Debugf("Caps lock toggled: %v", newState.CapsLock)
log.Debugf("Caps lock state: %v", newState.CapsLock)
m.notifySubscribers(newState)
}
@@ -195,10 +335,13 @@ func (m *Manager) Unsubscribe(id string) {
m.subMutex.Lock()
defer m.subMutex.Unlock()
if ch, ok := m.subscribers[id]; ok {
close(ch)
delete(m.subscribers, id)
ch, ok := m.subscribers[id]
if !ok {
return
}
close(ch)
delete(m.subscribers, id)
}
func (m *Manager) notifySubscribers(state State) {
@@ -217,11 +360,20 @@ func (m *Manager) Close() {
m.closeOnce.Do(func() {
close(m.closeChan)
if m.device != nil {
if err := m.device.Close(); err != nil && !isClosedError(err) {
if m.watcher != nil {
m.watcher.Close()
}
m.devicesMutex.Lock()
for _, device := range m.devices {
if device == nil {
continue
}
if err := device.Close(); err != nil && !isClosedError(err) {
log.Warnf("Error closing evdev device: %v", err)
}
}
m.devicesMutex.Unlock()
m.subMutex.Lock()
for id, ch := range m.subscribers {