From 3f2aadeeed264b6a310bb45b75aae3887e653dc0 Mon Sep 17 00:00:00 2001 From: fuleyi Date: Fri, 26 Dec 2025 11:18:57 +0800 Subject: [PATCH] feat: replace kernel file with udev for touchpad control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit replaces the kernel file-based touchpad control mechanism with a udev-based solution. The main changes include: 1. Removed dependency on /proc/uos/touchpad_switch kernel file 2. Implemented udev rules to control touchpad via LIBINPUT_IGNORE_DEVICE environment variable 3. Added udev monitor to track touchpad device changes in real-time 4. Enhanced system touchpad integration with proper DBus property synchronization 5. Improved channel handling in transient units to prevent blocking The new approach uses udev rules to set LIBINPUT_IGNORE_DEVICE=1 for touchpad devices when disabled, which is more reliable and portable across different kernel versions. The system now properly monitors device changes and synchronizes touchpad state between user session and system level. Log: Touchpad control now uses udev rules instead of kernel files Influence: 1. Test touchpad enable/disable functionality through settings 2. Verify touchpad state persists after reboot 3. Test touchpad hotplug detection (plug/unplug external touchpad) 4. Verify touchpad toggle keyboard shortcuts work correctly 5. Test touchpad behavior when mouse is connected/disconnected 6. Check system logs for any udev-related errors feat: 使用 udev 替代内核文件控制触控板 本次提交将基于内核文件的触控板控制机制替换为基于 udev 的解决方案。主要变 更包括: 1. 移除对 /proc/uos/touchpad_switch 内核文件的依赖 2. 实现 udev 规则通过 LIBINPUT_IGNORE_DEVICE 环境变量控制触控板 3. 添加 udev 监视器实时跟踪触控板设备变化 4. 增强系统触控板集成,实现正确的 DBus 属性同步 5. 改进瞬态单元中的通道处理以防止阻塞 新方法使用 udev 规则在禁用触控板时设置 LIBINPUT_IGNORE_DEVICE=1,这种方 法更可靠且可在不同内核版本间移植。系统现在能正确监控设备变化并在用户会话 和系统级别之间同步触控板状态。 Log: 触控板控制现在使用 udev 规则替代内核文件 PMS: BUG-342407 Influence: 1. 通过设置测试触控板启用/禁用功能 2. 验证重启后触控板状态持久化 3. 测试触控板热插拔检测(插拔外接触控板) 4. 验证触控板切换键盘快捷键正常工作 5. 测试连接/断开鼠标时的触控板行为 6. 检查系统日志中是否有 udev 相关错误 --- common/systemdunit/transientunit.go | 8 +- inputdevices1/ifc.go | 10 +- inputdevices1/inputdevices_dbusutil.go | 13 ++ inputdevices1/mouse.go | 4 +- inputdevices1/touchpad.go | 129 ++++++------- system/inputdevices1/libinput.go | 2 +- system/inputdevices1/touchpad.go | 207 ++++++++++++++++----- system/inputdevices1/udev_monitor.go | 247 +++++++++++++++++++++++++ system/keyevent1/manager.go | 80 ++------ 9 files changed, 514 insertions(+), 186 deletions(-) create mode 100644 system/inputdevices1/udev_monitor.go diff --git a/common/systemdunit/transientunit.go b/common/systemdunit/transientunit.go index 7dae7bed8..881b3cccd 100644 --- a/common/systemdunit/transientunit.go +++ b/common/systemdunit/transientunit.go @@ -81,12 +81,16 @@ func (t *TransientUnit) WaitforFinish(sigLoop *dbusutil.SignalLoop) bool { return false } t.unit.InitSignalExt(sigLoop, true) - var result = make(chan string) + var result = make(chan string, 1) // buffered channel to prevent blocking t.unit.ConnectPropertiesChanged(func(interfaceName string, changedProperties map[string]dbus.Variant, invalidatedProperties []string) { _, ok := changedProperties["ActiveState"] if ok { val := changedProperties["ActiveState"].String() - result <- val + select { + case result <- val: + default: + // channel is full or closed, skip this update + } } }) defer func() { diff --git a/inputdevices1/ifc.go b/inputdevices1/ifc.go index f0cf92748..50ce5476b 100644 --- a/inputdevices1/ifc.go +++ b/inputdevices1/ifc.go @@ -5,6 +5,8 @@ package inputdevices import ( + "fmt" + "github.com/godbus/dbus/v5" langselector "github.com/linuxdeepin/dde-daemon/langselector1" "github.com/linuxdeepin/go-lib/dbusutil" @@ -31,8 +33,12 @@ func (tpad *Touchpad) Reset() *dbus.Error { } func (tpad *Touchpad) Enable(enabled bool) *dbus.Error { - tpad.enable(enabled) - return nil + sysTP := tpad.sysTouchPad + if sysTP != nil { + return dbusutil.ToError(sysTP.SetTouchpadEnable(0, enabled)) + } else { + return dbusutil.ToError(fmt.Errorf("system touchpad is nul")) + } } func (w *Wacom) Reset() *dbus.Error { diff --git a/inputdevices1/inputdevices_dbusutil.go b/inputdevices1/inputdevices_dbusutil.go index d1ccc6aeb..dfc5b67c7 100644 --- a/inputdevices1/inputdevices_dbusutil.go +++ b/inputdevices1/inputdevices_dbusutil.go @@ -76,6 +76,19 @@ func (v *Touchpad) emitPropChangedDeviceList(value string) error { return v.service.EmitPropertyChanged(v, "DeviceList", value) } +func (v *Touchpad) setPropTPadEnable(value bool) (changed bool) { + if v.TPadEnable != value { + v.TPadEnable = value + v.emitPropChangedTPadEnable(value) + return true + } + return false +} + +func (v *Touchpad) emitPropChangedTPadEnable(value bool) error { + return v.service.EmitPropertyChanged(v, "TPadEnable", value) +} + func (v *TrackPoint) setPropDeviceList(value string) (changed bool) { if v.DeviceList != value { v.DeviceList = value diff --git a/inputdevices1/mouse.go b/inputdevices1/mouse.go index 0ff78b6e6..119ed1169 100644 --- a/inputdevices1/mouse.go +++ b/inputdevices1/mouse.go @@ -95,7 +95,7 @@ func (m *Mouse) init() { tpad := m.touchPad if !m.Exist { - if tpad.Exist && tpad.TPadEnable.Get() { + if tpad.Exist && tpad.TPadEnable { tpad.setDisableTemporary(false) } return @@ -107,7 +107,7 @@ func (m *Mouse) init() { m.enableAdaptiveAccelProfile() m.motionAcceleration() m.motionThreshold() - if m.DisableTpad.Get() && tpad.TPadEnable.Get() { + if m.DisableTpad.Get() && tpad.TPadEnable { m.disableTouchPad() } } diff --git a/inputdevices1/touchpad.go b/inputdevices1/touchpad.go index 8f2872de2..c2d8c388e 100644 --- a/inputdevices1/touchpad.go +++ b/inputdevices1/touchpad.go @@ -50,13 +50,14 @@ const ( ) type Touchpad struct { - service *dbusutil.Service - PropsMu sync.RWMutex - Exist bool - DeviceList string + service *dbusutil.Service + PropsMu sync.RWMutex + sysTouchPad inputdevices.Touchpad + Exist bool + DeviceList string // dbusutil-gen: ignore-below - TPadEnable dconfig.Bool `prop:"access:rw"` + TPadEnable bool `prop:"access:rw"` // 通过监听 system 端 Enable 属性动态更新,不使用 dconfig LeftHanded dconfig.Bool `prop:"access:rw"` DisableIfTyping dconfig.Bool `prop:"access:rw"` NaturalScroll dconfig.Bool `prop:"access:rw"` @@ -98,7 +99,6 @@ func newTouchpad(service *dbusutil.Service) *Touchpad { panic("Touchpad DConfig initialization failed - cannot continue without dconfig support") } - tpad.TPadEnable.Bind(tpad.dsgTouchpadConfig, dconfigKeyTouchpadEnabled) tpad.LeftHanded.Bind(tpad.dsgTouchpadConfig, dconfigKeyTouchpadLeftHanded) tpad.DisableIfTyping.Bind(tpad.dsgTouchpadConfig, dconfigKeyTouchpadDisableTyping) tpad.NaturalScroll.Bind(tpad.dsgTouchpadConfig, dconfigKeyTouchpadNaturalScroll) @@ -117,18 +117,47 @@ func newTouchpad(service *dbusutil.Service) *Touchpad { tpad.DoubleClick.Bind(tpad.dsgMouseConfig, dconfigKeyDoubleClick) tpad.DragThreshold.Bind(tpad.dsgMouseConfig, dconfigKeyDragThreshold) - // TODO: treeland环境暂不支持 - if hasTreeLand { - return tpad - } - tpad.updateDXTpads() - if conn, err := dbus.SystemBus(); err != nil { logger.Warning(err) } else { tpad.systemConn = conn tpad.systemSigLoop = dbusutil.NewSignalLoop(conn, 10) + tpad.sysTouchPad, err = inputdevices.NewTouchpad(tpad.systemConn, "/org/deepin/dde/InputDevices1/Touchpad") + if err != nil { + logger.Warning(err) + } else { + // 在这里注册信号监听,避免 init 中重复注册 + tpad.sysTouchPad.InitSignalExt(tpad.systemSigLoop, true) + tpad.sysTouchPad.Enable().ConnectChanged(func(hasValue bool, value bool) { + if !hasValue { + return + } + logger.Infof("System touchpad Enable changed: %v", value) + tpad.enable(value) + }) + // 因为通过udev禁用触控板时, libinput感知不到触控板,通过系统touchpad查询是否存在触控板设备 + tpad.sysTouchPad.DeviceList().ConnectChanged(func(hasValue bool, value []string) { + if !hasValue { + return + } + logger.Infof("System touchpad DeviceList changed: %v", value) + if !tpad.TPadEnable { + if len(value) == 0 { + tpad.setPropExist(false) + } else { + tpad.setPropExist(true) + } + } + }) + tpad.systemSigLoop.Start() + tpad.TPadEnable, _ = tpad.sysTouchPad.Enable().Get(0) + } + } + // TODO: treeland环境暂不支持 + if hasTreeLand { + return tpad } + tpad.updateDXTpads() return tpad } @@ -180,28 +209,15 @@ func (tpad *Touchpad) init() { return } - if tpad.systemConn != nil { - sysTouchPad, err := inputdevices.NewTouchpad(tpad.systemConn, "/org/deepin/dde/InputDevices1/Touchpad") - if err != nil { + // 从 system 端获取初始状态 + if tpad.sysTouchPad != nil { + if enabled, err := tpad.sysTouchPad.Enable().Get(0); err != nil { logger.Warning(err) } else { - sysTouchPad.InitSignalExt(tpad.systemSigLoop, true) - sysTouchPad.Enable().ConnectChanged(func(hasValue bool, value bool) { - if !hasValue { - return - } - tpad.enable(value) - }) - if enabled, err := sysTouchPad.Enable().Get(0); err != nil { - logger.Warning(err) - } else { - tpad.TPadEnable.Set(enabled) - } + tpad.setPropTPadEnable(enabled) } } - currentState := tpad.TPadEnable.Get() - tpad.forceEnable(currentState) tpad.enableLeftHanded() tpad.enableNaturalScroll() tpad.enableEdgeScroll() @@ -213,10 +229,6 @@ func (tpad *Touchpad) init() { tpad.disableWhileTyping() tpad.enablePalmDetect() tpad.setPalmDimensions() - - if tpad.systemSigLoop != nil { - tpad.systemSigLoop.Start() - } } func (tpad *Touchpad) handleDeviceChanged() { @@ -238,44 +250,46 @@ func (tpad *Touchpad) updateDXTpads() { tpad.PropsMu.Lock() var v string - if len(tpad.devInfos) == 0 { - tpad.setPropExist(false) + if tpad.TPadEnable { + if len(tpad.devInfos) == 0 { + tpad.setPropExist(false) + } else { + tpad.setPropExist(true) + v = tpad.devInfos.string() + } } else { - tpad.setPropExist(true) - v = tpad.devInfos.string() + if tpad.sysTouchPad != nil { + sysTpList, _ := tpad.sysTouchPad.DeviceList().Get(0) + tpad.setPropExist(len(sysTpList) > 0) + } } + tpad.setPropDeviceList(v) tpad.PropsMu.Unlock() } // 受鼠标禁用触控板影响,临时关闭触控板 func (tpad *Touchpad) setDisableTemporary(disable bool) { - if disable == tpad.disableTemporary { - return - } if len(tpad.devInfos) > 0 { for _, v := range tpad.devInfos { - err := v.Enable(!disable && tpad.TPadEnable.Get()) + err := v.Enable(!disable && tpad.TPadEnable) if err != nil { logger.Warningf("Enable '%v - %v' failed: %v", v.Id, v.Name, err) } } } - tpad.disableTemporary = disable } func (tpad *Touchpad) enable(enabled bool) { - if enabled == tpad.TPadEnable.Get() { + if enabled == tpad.TPadEnable { return } - tpad.forceEnable(enabled) -} - -func (tpad *Touchpad) forceEnable(enabled bool) { - if len(tpad.devInfos) > 0 { + // 禁用时有system 的touchpad设置 + // 重新开启时,需要考虑插入鼠标时禁用触控板 + if len(tpad.devInfos) > 0 && enabled { for _, v := range tpad.devInfos { - err := v.Enable(!tpad.disableTemporary && enabled) + err := v.Enable(!tpad.disableTemporary) if err != nil { logger.Warningf("Enable '%v - %v' failed: %v", v.Id, v.Name, err) @@ -284,13 +298,7 @@ func (tpad *Touchpad) forceEnable(enabled bool) { } enableGesture(enabled) - tpad.TPadEnable.Set(enabled) - sysTouchPad, err := inputdevices.NewTouchpad(tpad.systemConn, "/org/deepin/dde/InputDevices1/Touchpad") - if err == nil && sysTouchPad != nil { - if err = sysTouchPad.SetTouchpadEnable(0, enabled); err != nil { - logger.Warning(err) - } - } + tpad.setPropTPadEnable(enabled) } func (tpad *Touchpad) enableLeftHanded() { @@ -544,7 +552,7 @@ func enableGesture(enabled bool) { return } - dconfig.SetValue("dconfig", enabled) + dconfig.SetValue("touchpadEnabled", enabled) } func (tpad *Touchpad) initTouchpadDConfig() error { @@ -562,13 +570,6 @@ func (tpad *Touchpad) initTouchpadDConfig() error { tpad.dsgTouchpadConfig.ConnectValueChanged(func(key string) { logger.Debugf("Touchpad dconfig value changed: %s", key) switch key { - case dconfigKeyTouchpadEnabled: - enabled, err := tpad.dsgTouchpadConfig.GetValueBool(dconfigKeyTouchpadEnabled) - if err != nil { - logger.Warningf("Failed to get touchpad enabled value from dconfig: %v", err) - } else { - tpad.enable(enabled) - } case dconfigKeyTouchpadLeftHanded: tpad.enableLeftHanded() case dconfigKeyTouchpadDisableTyping: diff --git a/system/inputdevices1/libinput.go b/system/inputdevices1/libinput.go index ec468c9ae..3123e6f2e 100644 --- a/system/inputdevices1/libinput.go +++ b/system/inputdevices1/libinput.go @@ -25,7 +25,7 @@ func log_handler_go(priority C.enum_libinput_log_priority, cstr *C.char) { case C.LIBINPUT_LOG_PRIORITY_INFO: logger.Info(str) case C.LIBINPUT_LOG_PRIORITY_ERROR: - logger.Error(str) + logger.Warning(str) } } diff --git a/system/inputdevices1/touchpad.go b/system/inputdevices1/touchpad.go index 65d202321..5419ddf97 100644 --- a/system/inputdevices1/touchpad.go +++ b/system/inputdevices1/touchpad.go @@ -5,8 +5,9 @@ package inputdevices1 import ( - "errors" "os" + "os/exec" + "path/filepath" "strings" "github.com/godbus/dbus/v5" @@ -15,15 +16,24 @@ import ( ) const ( - // touchpad接口的导出不再依赖touchpadSwitchFile文件的存在,只有设置的时候才会读写该文件配置 - touchpadSwitchFile = "/proc/uos/touchpad_switch" touchpadDBusPath = "/org/deepin/dde/InputDevices1/Touchpad" touchpadDBusInterface = "org.deepin.dde.InputDevices1.Touchpad" + + // udev 规则文件路径 + udevRuleFile = "/etc/udev/rules.d/90-dde-touchpad.rules" + // udev 规则内容(禁用触控板) + // ENV{LIBINPUT_IGNORE_DEVICE}="1": 让 libinput 忽略设备 + // 注意:这只影响 libinput,evtest 仍能读取内核事件 + udevRuleContent = `# DDE - Disable touchpad via libinput +SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_TOUCHPAD}=="1", ENV{LIBINPUT_IGNORE_DEVICE}="1" +` ) type Touchpad struct { - service *dbusutil.Service - Enable bool + service *dbusutil.Service + Enable bool + DeviceList []string + udevMonitor *udevMonitor } func newTouchpad(service *dbusutil.Service) *Touchpad { @@ -31,43 +41,161 @@ func newTouchpad(service *dbusutil.Service) *Touchpad { service: service, Enable: getDsgConf(), } + + // 初始化 udev 监听器 + t.udevMonitor = newUdevMonitor(func(devices []string) { + t.handleDeviceChange(devices) + }) + + // 初始化设备列表 + if t.udevMonitor != nil { + devices := t.udevMonitor.enumerateDevices() + t.setPropDeviceList(devices) + logger.Infof("touchpad initialized with %d device(s)", len(devices)) + } + return t } +// handleDeviceChange 处理设备变化 +func (t *Touchpad) handleDeviceChange(devices []string) { + t.setPropDeviceList(devices) + logger.Infof("touchpad devices updated: %d device(s)", len(devices)) +} + func (t *Touchpad) SetTouchpadEnable(enabled bool) *dbus.Error { err := t.setTouchpadEnable(enabled) return dbusutil.ToError(err) } func (t *Touchpad) setTouchpadEnable(enabled bool) error { - t.setPropEnable(enabled) + logger.Debugf("setTouchpadEnable: %v", enabled) + if !t.setPropEnable(enabled) { + return nil + } + + // 1. 保存到 dconfig(持久化配置) err := setDsgConf(enabled) if err != nil { - logger.Warning(err) + logger.Warning("failed to save to dconfig:", err) return err } - if err = TouchpadExist(touchpadSwitchFile); err != nil { - return nil + // 2. 使用 udev 规则方案 + if err := t.setTouchpadEnableViaUdev(enabled); err != nil { + logger.Warning("udev rules method failed:", err) + return err } - current, err := TouchpadEnable(touchpadSwitchFile) - if err != nil { - logger.Warning(" TouchpadEnable err : ", err) + + return nil +} + +// setTouchpadEnableViaUdev 通过 udev 规则禁用/启用触控板 +func (t *Touchpad) setTouchpadEnableViaUdev(enabled bool) error { + if enabled { + // 启用:删除 udev 规则文件 + if err := os.Remove(udevRuleFile); err != nil && !os.IsNotExist(err) { + return err + } + logger.Info("removed udev rule file:", udevRuleFile) + } else { + // 禁用:检查文件是否已存在且内容相同 + existingContent, err := os.ReadFile(udevRuleFile) + if err == nil && string(existingContent) == udevRuleContent { + logger.Debug("udev rule file already exists with correct content, skip writing") + return nil + } + + // 创建或覆盖 udev 规则文件 + if err := os.WriteFile(udevRuleFile, []byte(udevRuleContent), 0644); err != nil { + return err + } + logger.Info("created udev rule file:", udevRuleFile) + } + + // 重新加载 udev 规则 + if err := reloadUdevRules(); err != nil { + logger.Warning("failed to reload udev rules:", err) + } + + // 只触发触控板设备,减少不必要的事件 + if err := t.triggerTouchpadDevices(); err != nil { + logger.Warning("failed to trigger touchpad devices:", err) + } + + return nil +} + +// reloadUdevRules 重新加载 udev 规则 +func reloadUdevRules() error { + cmd := exec.Command("udevadm", "control", "--reload-rules") + if err := cmd.Run(); err != nil { return err } - if current == enabled { - logger.Info("current touchPad state is same : ", enabled) + logger.Info("udev rules reloaded") + return nil +} + +// triggerTouchpadDevices 只触发触控板设备,减少不必要的事件 +func (t *Touchpad) triggerTouchpadDevices() error { + touchpadNames := t.DeviceList + if len(touchpadNames) == 0 { + logger.Warning("no touchpad devices to trigger") return nil } - arg := "enable" - if !enabled { - arg = "disable" - } - err = os.WriteFile(touchpadSwitchFile, []byte(arg), 0644) + + sysInputPath := "/sys/class/input" + files, err := os.ReadDir(sysInputPath) if err != nil { - logger.Warning(" os.WriteFile err : ", err) return err } + + count := 0 + for _, file := range files { + // 只处理 event 设备 + if !strings.HasPrefix(file.Name(), "event") { + continue + } + + // 读取设备名称 + namePath := filepath.Join(sysInputPath, file.Name(), "device/name") + nameBytes, err := os.ReadFile(namePath) + if err != nil { + continue + } + + name := strings.TrimSpace(string(nameBytes)) + + // 检查是否是触控板设备 + isTouchpad := false + for _, touchpadName := range touchpadNames { + if name == touchpadName { + isTouchpad = true + break + } + } + + if !isTouchpad { + continue + } + + // 只触发这个触控板设备 + cmd := exec.Command("udevadm", "trigger", "--action=change", "--sysname="+file.Name()) + if err := cmd.Run(); err != nil { + logger.Warningf("failed to trigger %s: %v", file.Name(), err) + } else { + count++ + logger.Debugf("triggered touchpad device: %s (%s)", file.Name(), name) + } + + } + + if count > 0 { + logger.Infof("triggered %d touchpad device(s)", count) + } else { + logger.Warning("no touchpad devices found to trigger") + } + return nil } @@ -113,30 +241,6 @@ func getDsgConf() bool { return data.Value().(bool) } -func TouchpadEnable(filePath string) (bool, error) { - err := TouchpadExist(filePath) - if err != nil { - return false, err - } - content, err := os.ReadFile(touchpadSwitchFile) - if err != nil { - return false, err - } - return strings.Contains(string(content), "enable"), nil -} - -func TouchpadExist(filePath string) error { - if filePath != touchpadSwitchFile { - return errors.New("filePath is inValid") - } - _, err := os.Stat(touchpadSwitchFile) - if err != nil { - logger.Warning(err) - return err - } - return nil -} - func (t *Touchpad) GetInterfaceName() string { return touchpadDBusInterface } @@ -145,6 +249,17 @@ func (t *Touchpad) export(path dbus.ObjectPath) error { return t.service.Export(path, t) } -func (t *Touchpad) stopExport() error { - return t.service.StopExport(t) +// setPropDeviceList 设置 DeviceList 属性并发送信号 +func (t *Touchpad) setPropDeviceList(devices []string) { + t.DeviceList = devices + // 发送属性变化信号 + _ = t.service.EmitPropertyChanged(t, "DeviceList", devices) +} + +// destroy 销毁触控板对象 +func (t *Touchpad) destroy() { + if t.udevMonitor != nil { + t.udevMonitor.destroy() + t.udevMonitor = nil + } } diff --git a/system/inputdevices1/udev_monitor.go b/system/inputdevices1/udev_monitor.go new file mode 100644 index 000000000..adb4ebbf7 --- /dev/null +++ b/system/inputdevices1/udev_monitor.go @@ -0,0 +1,247 @@ +// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package inputdevices1 + +// #cgo pkg-config: libudev +// #include +// #include +// #include +// #include +// +// // 永久阻塞等待 udev 事件(-1 表示永不超时) +// static struct udev_device* receive_device_blocking(struct udev_monitor *monitor) { +// struct pollfd pfd; +// pfd.fd = udev_monitor_get_fd(monitor); +// pfd.events = POLLIN; +// +// int ret = poll(&pfd, 1, -1); // -1 = 永不超时 +// if (ret > 0) { +// return udev_monitor_receive_device(monitor); +// } +// return NULL; // 只有出错才会到这里 +// } +import "C" +import ( + "strings" + "unsafe" +) + +// udevMonitor 使用 libudev 监听输入设备的插拔 +type udevMonitor struct { + udev *C.struct_udev + monitor *C.struct_udev_monitor + stopCh chan struct{} + callback func(devices []string) +} + +// newUdevMonitor 创建 udev 监听器 +func newUdevMonitor(callback func(devices []string)) *udevMonitor { + // 创建 udev 上下文 + udev := C.udev_new() + if udev == nil { + logger.Warning("failed to create udev context") + return nil + } + + // 创建 udev 监听器 + monitor := C.udev_monitor_new_from_netlink(udev, C.CString("udev")) + if monitor == nil { + logger.Warning("failed to create udev monitor") + C.udev_unref(udev) + return nil + } + + // 过滤:只监听 input 子系统 + subsystem := C.CString("input") + C.udev_monitor_filter_add_match_subsystem_devtype(monitor, subsystem, nil) + C.free(unsafe.Pointer(subsystem)) + + // 启用监听器 + if C.udev_monitor_enable_receiving(monitor) < 0 { + logger.Warning("failed to enable udev monitor") + C.udev_monitor_unref(monitor) + C.udev_unref(udev) + return nil + } + + m := &udevMonitor{ + udev: udev, + monitor: monitor, + stopCh: make(chan struct{}), + callback: callback, + } + + // 启动监听 goroutine + go m.handleEvents() + + logger.Info("udev monitor started (using libudev)") + return m +} + +// handleEvents 处理 udev 事件(永久阻塞式) +func (m *udevMonitor) handleEvents() { + // 使用 channel 接收设备事件 + deviceCh := make(chan *C.struct_udev_device, 10) + + // 启动阻塞读取 goroutine + go func() { + for { + // 永久阻塞等待事件,不占用 CPU + device := C.receive_device_blocking(m.monitor) + if device != nil { + select { + case deviceCh <- device: + case <-m.stopCh: + C.udev_device_unref(device) + return + } + } + } + }() + + // 主循环处理事件或停止信号 + for { + select { + case <-m.stopCh: + return + case device := <-deviceCh: + m.handleDevice(device) + C.udev_device_unref(device) + } + } +} + +// handleDevice 处理单个设备事件 +func (m *udevMonitor) handleDevice(device *C.struct_udev_device) { + // 获取事件类型 + action := C.GoString(C.udev_device_get_action(device)) + + // 只关心 add 和 remove 事件 + if action != "add" && action != "remove" { + return + } + + // 获取设备节点 + devnode := C.GoString(C.udev_device_get_devnode(device)) + if devnode == "" || !strings.Contains(devnode, "/dev/input/event") { + return + } + + // 检查是否是触控板 + if !isTouchpadDeviceUdev(device) { + return + } + + logger.Debugf("udev: %s event for %s", action, devnode) + + // 重新扫描所有设备并回调 + if m.callback != nil { + devices := m.enumerateDevices() + m.callback(devices) + } +} + +// isTouchpadDeviceUdev 判断 udev 设备是否是触控板 +func isTouchpadDeviceUdev(device *C.struct_udev_device) bool { + // 方法1:检查 ID_INPUT_TOUCHPAD 属性(最准确) + propKey := C.CString("ID_INPUT_TOUCHPAD") + defer C.free(unsafe.Pointer(propKey)) + prop := C.udev_device_get_property_value(device, propKey) + if prop != nil && C.GoString(prop) == "1" { + return true + } + + // 方法2:通过设备名称判断(降级方案) + nameKey := C.CString("device/name") + defer C.free(unsafe.Pointer(nameKey)) + name := C.udev_device_get_sysattr_value(device, nameKey) + if name != nil { + devName := C.GoString(name) + nameLower := strings.ToLower(devName) + return strings.Contains(nameLower, "touchpad") || isTPadPS2Mouse(nameLower) + } + + return false +} + +// isTPadPS2Mouse 判断是否是 PS/2 触控板 +func isTPadPS2Mouse(name string) bool { + return strings.Contains(name, "ps/2") && strings.Contains(name, "mouse") && !strings.Contains(name, "usb") +} + +// enumerateDevices 枚举所有触控板设备 +func (m *udevMonitor) enumerateDevices() []string { + var devices []string + + // 创建枚举器 + enumerate := C.udev_enumerate_new(m.udev) + if enumerate == nil { + logger.Warning("failed to create udev enumerate") + return devices + } + defer C.udev_enumerate_unref(enumerate) + + // 过滤:只枚举 input 子系统 + subsystem := C.CString("input") + C.udev_enumerate_add_match_subsystem(enumerate, subsystem) + C.free(unsafe.Pointer(subsystem)) + + // 扫描设备 + C.udev_enumerate_scan_devices(enumerate) + + // 获取设备列表 + deviceMap := make(map[string]bool) + entry := C.udev_enumerate_get_list_entry(enumerate) + for entry != nil { + // 获取设备路径 + syspath := C.udev_list_entry_get_name(entry) + device := C.udev_device_new_from_syspath(m.udev, syspath) + + if device != nil { + // 只处理 event 设备 + devnode := C.GoString(C.udev_device_get_devnode(device)) + if devnode != "" && strings.Contains(devnode, "/dev/input/event") { + // 检查是否是触控板 + if isTouchpadDeviceUdev(device) { + // 获取设备名称 + nameKey := C.CString("device/name") + name := C.udev_device_get_sysattr_value(device, nameKey) + C.free(unsafe.Pointer(nameKey)) + if name != nil { + devName := strings.TrimSpace(C.GoString(name)) + if devName != "" && !deviceMap[devName] { + deviceMap[devName] = true + devices = append(devices, devName) + } + } + } + } + C.udev_device_unref(device) + } + + entry = C.udev_list_entry_get_next(entry) + } + + return devices +} + +// destroy 销毁监听器 +func (m *udevMonitor) destroy() { + if m.stopCh != nil { + close(m.stopCh) + } + + if m.monitor != nil { + C.udev_monitor_unref(m.monitor) + m.monitor = nil + } + + if m.udev != nil { + C.udev_unref(m.udev) + m.udev = nil + } + + logger.Info("udev monitor stopped") +} diff --git a/system/keyevent1/manager.go b/system/keyevent1/manager.go index 80b03550e..04d10876c 100644 --- a/system/keyevent1/manager.go +++ b/system/keyevent1/manager.go @@ -5,10 +5,6 @@ package keyevent1 import ( - "errors" - "os" - "strings" - "github.com/godbus/dbus/v5" inputdevices "github.com/linuxdeepin/go-dbus-factory/system/org.deepin.dde.inputdevices1" "github.com/linuxdeepin/go-lib/dbusutil" @@ -147,77 +143,23 @@ func (m *Manager) handleEvent(ev *KeyEvent) { if !pressed { return } - - //开关触控板逻辑 - _, err := os.Stat(touchpadSwitchFile) - if err != nil { - logger.Warning(err) + var TPadEnable bool + if m.touchPad != nil { + TPadEnable, _ = m.touchPad.Enable().Get(0) + } else { return } switch ev.Keycode { case KEY_TOUCHPAD_TOGGLE: - go func() { - content, err := os.ReadFile(touchpadSwitchFile) - if err != nil { - logger.Warning(err) - return - } - enable := strings.Contains(string(content), "enable") - - if m.touchPad == nil { - err = errors.New("m.TouchPad is nil") - } else { - err = m.touchPad.SetTouchpadEnable(0, !enable) - } - - if err != nil { - logger.Warning("Set TouchPad state err : ", err) - - // 接口调用异常时,需要保证开关触摸板正常 - arg := string(content) - if strings.Contains(arg, "enable") { - arg = "disable" - } else { - arg = "enable" - } - err = os.WriteFile(touchpadSwitchFile, []byte(arg), 0644) - if err != nil { - logger.Warning("write /proc/uos/touchpad_switch err : ", err) - } - } - }() + m.touchPad.SetTouchpadEnable(0, !TPadEnable) case KEY_TOUCHPAD_ON: - go func() { - if m.touchPad == nil { - err = errors.New("m.TouchPad is nil") - } else { - err = m.touchPad.SetTouchpadEnable(0, true) - } - - if err != nil { - logger.Warning("Set TouchPad state err : ", err) - err = os.WriteFile(touchpadSwitchFile, []byte("enable"), 0644) - if err != nil { - logger.Warning("write /proc/uos/touchpad_switch err : ", err) - } - } - }() + if !TPadEnable { + m.touchPad.SetTouchpadEnable(0, true) + } case KEY_TOUCHPAD_OFF: - go func() { - if m.touchPad == nil { - err = errors.New("m.TouchPad is nil") - } else { - err = m.touchPad.SetTouchpadEnable(0, false) - } - - if err != nil { - logger.Warning("Set TouchPad state err : ", err) - err = os.WriteFile(touchpadSwitchFile, []byte("disable"), 0644) - if err != nil { - logger.Warning("write /proc/uos/touchpad_switch err : ", err) - } - } - }() + if TPadEnable { + m.touchPad.SetTouchpadEnable(0, false) + } } } }