Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions keybinding1/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"sync"
"time"

dbus "github.com/godbus/dbus/v5"
Expand Down Expand Up @@ -128,6 +129,9 @@ type Manager struct {
sessionSigLoop *dbusutil.SignalLoop
systemSigLoop *dbusutil.SignalLoop
//startManager sessionmanager.StartManager

// 记录当前正在通过 API 修改的 shortcuts,避免和 dconfig 监听竞争
modifyingShortcuts *StringSet
sessionManager sessionmanager.SessionManager
airplane airplanemode.AirplaneMode
networkmanager networkmanager.Manager
Expand Down Expand Up @@ -238,6 +242,7 @@ func newManager(service *dbusutil.Service) (*Manager, error) {
conn: conn,
keySymbols: keysyms.NewKeySymbols(conn),
handlers: make([]shortcuts.KeyEventFunc, shortcuts.ActionTypeCount),
modifyingShortcuts: NewStringSet(),
}

m.sessionSigLoop = dbusutil.NewSignalLoop(sessionBus, 10)
Expand Down Expand Up @@ -663,6 +668,7 @@ func (m *Manager) listenGlobalAccel(sessionBus *dbus.Conn) error {
m.shortcutKey = sig.Body[0].(string)
m.shortcutKeyCmd = sig.Body[1].(string)
shortId := kwinSysActionCmdMap[m.shortcutKeyCmd]
logger.Infof("[keybinding][wayland] globalShortcutPressed component=%q action=%q mappedId=%q", m.shortcutKey, m.shortcutKeyCmd, shortId)
if shortId != "" && m.DisabledSystemShortcutsList.Contains(shortId) {
logger.Warningf("shortcut id: %s is disabled", shortId)
return
Expand All @@ -678,6 +684,7 @@ func (m *Manager) listenGlobalAccel(sessionBus *dbus.Conn) error {
if m.shortcutCmd == "" {
m.shortcutCmd = m.shortcutManager.WaylandCustomShortCutMap[m.shortcutKeyCmd]
}
logger.Infof("[keybinding][wayland] resolved action=%q mappedId=%q cmd=%q", m.shortcutKeyCmd, kwinSysActionCmdMap[m.shortcutKeyCmd], m.shortcutCmd)
logger.Debug("WaylandCustomShortCutMap", m.shortcutCmd)
if m.shortcutCmd == "" {
m.handleKeyEventByWayland(waylandMediaIdMap[m.shortcutKeyCmd])
Expand Down Expand Up @@ -1239,6 +1246,12 @@ func (m *Manager) listenDConfigChanged(config configManager.Manager, type0 int32
if !m.enableListenDConfig {
return
}
// 如果当前正在通过 API 修改这个 shortcut,跳过监听回调
// 避免控制中心的 Add/Clear 操作和 dconfig 监听之间的竞争
if m.modifyingShortcuts != nil && m.modifyingShortcuts.Has(key) {
logger.Debugf("listenDConfigChanged skipping %s, currently being modified via API", key)
return
}

shortcut := m.shortcutManager.GetByIdType(key, type0)
if shortcut == nil {
Expand All @@ -1256,6 +1269,24 @@ func (m *Manager) listenDConfigChanged(config configManager.Manager, type0 int32
return
}
m.shortcutManager.ModifyShortcutKeystrokes(shortcut, shortcuts.ParseKeystrokes(keystrokes))
if _useWayland && type0 == shortcuts.ShortcutTypeSystem {
keystrokes = shortcuts.NormalizeSystemKeystrokesForKWin(key, keystrokes)
accelJson, err := json.Marshal(struct {
Id string `json:"Id"`
Keystrokes []string `json:"Accels"`
}{
Id: key,
Keystrokes: keystrokes,
})
if err != nil {
logger.Warning("failed to marshal KWin accel json:", err)
} else {
ok, setErr := m.wm.SetAccel(0, string(accelJson))
if !ok {
logger.Warning("failed to update KWin accel:", key, keystrokes, setErr)
}
}
}
m.emitShortcutSignal(shortcutSignalChanged, shortcut)
})
}
Expand Down Expand Up @@ -1426,3 +1457,35 @@ func (m *Manager) eliminateKeystrokeConflict() {
m.shortcutManager.ConflictingKeystrokes = nil
m.shortcutManager.EliminateConflictDone = true
}

// StringSet 是一个简单的字符串集合,用于并发安全的标记

type StringSet struct {
mu sync.RWMutex
m map[string]struct{}
}

func NewStringSet() *StringSet {
return &StringSet{
m: make(map[string]struct{}),
}
}

func (s *StringSet) Add(key string) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[key] = struct{}{}
}

func (s *StringSet) Remove(key string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.m, key)
}

func (s *StringSet) Has(key string) bool {
s.mu.RLock()
defer s.mu.RUnlock()
_, ok := s.m[key]
return ok
}
57 changes: 50 additions & 7 deletions keybinding1/manager_ifc.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,31 +73,42 @@ func (m *Manager) setAccelForWayland(config configManager.Manager, wmObj wm.Wm)
for _, id := range keys {
var accelJson string
var err error

// 特殊处理:这些快捷键需要固定的 KWin 格式
if id == "screenshotWindow" {
accelJson = `{"Id":"screenshotWindow","Accels":["SysReq"]}` //+ Alt+print对应kwin识别的键SysReq
accelJson = `{"Id":"screenshotWindow","Accels":["SysReq"]}` // Alt+print对应kwin识别的键SysReq
} else if id == "launcher" {
accelJson = `{"Id":"launcher","Accels":["Super_L"]}` // wayland左右super对应的都是Super_L
} else if id == "system_monitor" {
accelJson = `{"Id":"system_monitor","Accels":["<Crtl><Alt>Escape"]}`
} else if id == "systemMonitor" {
accelJson = `{"Id":"systemMonitor","Accels":["<Crtl><Alt>Escape"]}`
} else {
KeystrokesValue, err := config.Value(0, id)
keystrokesValue, err := config.Value(0, id)
if err != nil {
logger.Warning("failed to get value:", err)
continue
}

keystrokes, err := shortcuts.ConvertToStringSliceForWayland(keystrokesValue.Value())
if err != nil {
logger.Warning("failed to convert keystrokes:", err)
continue
}
keystrokes = shortcuts.NormalizeSystemKeystrokesForKWin(id, keystrokes)

accelJson, err = util.MarshalJSON(util.KWinAccel{
Id: id,
Keystrokes: KeystrokesValue.Value().([]string),
Keystrokes: keystrokes,
})
if err != nil {
logger.Warning("failed to get json:", err)
continue
}
}

logger.Infof("[keybinding][wayland] setAccelForWayland id=%q", id)
ok, err := wmObj.SetAccel(0, accelJson)
if !ok {
logger.Warning("failed to set KWin accels:", err)
logger.Warning("failed to set KWin accels:", id, err)
}
}
}
Expand Down Expand Up @@ -288,6 +299,11 @@ func (m *Manager) DeleteCustomShortcut(id string) *dbus.Error {

func (m *Manager) ClearShortcutKeystrokes(id string, type0 int32) *dbus.Error {
logger.Debug("ClearShortcutKeystrokes", id, type0)
// 标记正在修改,避免 dconfig 监听回调干扰
if m.modifyingShortcuts != nil {
m.modifyingShortcuts.Add(id)
defer m.modifyingShortcuts.Remove(id)
}
shortcut := m.shortcutManager.GetByIdType(id, type0)
if shortcut == nil {
return dbusutil.ToError(ErrShortcutNotFound{id, type0})
Expand Down Expand Up @@ -417,6 +433,11 @@ func (m *Manager) ModifyCustomShortcut(id, name, cmd, keystroke string) *dbus.Er

func (m *Manager) AddShortcutKeystroke(id string, type0 int32, keystroke string) *dbus.Error {
logger.Debug("AddShortcutKeystroke", id, type0, keystroke)
// 标记正在修改,避免 dconfig 监听回调干扰
if m.modifyingShortcuts != nil {
m.modifyingShortcuts.Add(id)
defer m.modifyingShortcuts.Remove(id)
}
shortcut := m.shortcutManager.GetByIdType(id, type0)
if shortcut == nil {
return dbusutil.ToError(ErrShortcutNotFound{id, type0})
Expand Down Expand Up @@ -467,6 +488,25 @@ func (m *Manager) AddShortcutKeystroke(id string, type0 int32, keystroke string)
}
}

// 对于系统快捷键,如果已有 keystrokes 且不是 Delete 相关,则清空
// Delete 类快捷键需要保留双绑(Delete + KP_Delete)
if type0 == shortcuts.ShortcutTypeSystem && len(shortcut.GetKeystrokes()) > 0 {
// 检查是否涉及 Delete 键
hasDelete := false
for _, ks := range shortcut.GetKeystrokes() {
if strings.Contains(ks.String(), "Delete") {
hasDelete = true
break
}
}
if !hasDelete {
// 非 Delete 类系统快捷键,清空后添加
m.shortcutManager.ModifyShortcutKeystrokes(shortcut, nil)
} else {
// Delete 类快捷键保留双绑
}
}

// 添加所有 keystroke
for _, ksToAdd := range keystrokesToAdd {
m.shortcutManager.AddShortcutKeystroke(shortcut, ksToAdd)
Expand All @@ -484,7 +524,7 @@ func (m *Manager) AddShortcutKeystroke(id string, type0 int32, keystroke string)
}

func (m *Manager) DeleteShortcutKeystroke(id string, type0 int32, keystroke string) *dbus.Error {
logger.Debug("DeleteShortcutKeystroke", id, type0, keystroke)
logger.Infof("[keybinding] DeleteShortcutKeystroke id=%q type=%d keystroke=%q", id, type0, keystroke)
shortcut := m.shortcutManager.GetByIdType(id, type0)
if shortcut == nil {
return dbusutil.ToError(ErrShortcutNotFound{id, type0})
Expand All @@ -500,11 +540,14 @@ func (m *Manager) DeleteShortcutKeystroke(id string, type0 int32, keystroke stri
}
logger.Debug("keystroke:", ks.DebugString())

logger.Infof("[keybinding] DeleteShortcutKeystroke before delete: id=%q currentKeystrokes=%v", id, shortcut.GetKeystrokes())
m.shortcutManager.DeleteShortcutKeystroke(shortcut, ks)
logger.Infof("[keybinding] DeleteShortcutKeystroke after delete: id=%q remainingKeystrokes=%v", id, shortcut.GetKeystrokes())
err = shortcut.SaveKeystrokes()
if err != nil {
return dbusutil.ToError(err)
}
logger.Infof("[keybinding] DeleteShortcutKeystroke after save: id=%q savedKeystrokes=%v", id, shortcut.GetKeystrokes())
if shortcut.ShouldEmitSignalChanged() {
m.emitShortcutSignal(shortcutSignalChanged, shortcut)
}
Expand Down
60 changes: 43 additions & 17 deletions keybinding1/shortcuts/shortcut_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ func convertToStringSlice(value interface{}) ([]string, error) {
return nil, fmt.Errorf("unsupported type for string slice conversion: %T", value)
}

func ConvertToStringSliceForWayland(value interface{}) ([]string, error) {
return convertToStringSlice(value)
}

func NewShortcutManager(conn *x.Conn, keySymbols *keysyms.KeySymbols, eventCb KeyEventFunc) *ShortcutManager {
setUseWayland(strings.Contains(os.Getenv("XDG_SESSION_TYPE"), "wayland"))
ss := &ShortcutManager{
Expand Down Expand Up @@ -541,9 +545,10 @@ func (sm *ShortcutManager) storeConflictingKeystroke(ks *Keystroke) {
func (sm *ShortcutManager) grabKeystroke(shortcut Shortcut, ks *Keystroke, dummy bool) {
keyList, err := ks.ToKeyList(sm.keySymbols)
if err != nil {
logger.Debugf("grabKeystroke failed, shortcut: %v, ks: %v, err: %v", shortcut.GetId(), ks, err)
logger.Warningf("[keybinding] grabKeystroke failed, shortcut: %v, ks: %v, err: %v", shortcut.GetId(), ks, err)
return
}
logger.Debugf("grabKeystroke shortcut: %s, ks: %s, dummy: %v", shortcut.GetId(), ks, dummy)

var conflictCount int
var idx = -1
Expand Down Expand Up @@ -1342,6 +1347,7 @@ func setShortForWayland(shortcut Shortcut, wmObj wm.Wm) (bool, error) {
}
keystrokesStrv := shortcut.getKeystrokesStrv()
logger.Debugf("Id: %+v, keystrokesStrv: %+v", id, keystrokesStrv)
logger.Infof("[keybinding][wayland] setShortForWayland id=%q keystrokes=%v isCustom=%v", id, keystrokesStrv, isCustom)
accelJson, err := util.MarshalJSON(util.KWinAccel{
Id: id,
Keystrokes: keystrokesStrv,
Expand Down Expand Up @@ -1469,6 +1475,27 @@ func (sm *ShortcutManager) AddKWinForWayland(wmObj wm.Wm) {
}
}

func NormalizeSystemKeystrokesForKWin(id string, keystrokes []string) []string {
result := append([]string(nil), keystrokes...)

for i := 0; i < len(result); i++ {
result[i] = strings.Replace(result[i], "Del", "Delete", 1)
result[i] = strings.Replace(result[i], "Esc", "Escape", 1)
}

if id == "screenshotWindow" {
return []string{"SysReq"} // Alt+Print 对应 kwin 识别的键 SysReq
}
if id == "launcher" {
return []string{"Super_L"} // wayland左右super对应的都是Super_L
}
if id == "systemMonitor" {
return []string{"<Crtl><Alt>Escape"} // KWin需要这个特定格式
}

return result
}

func (sm *ShortcutManager) AddSystemToKwin(wmObj wm.Wm) {
logger.Debug("AddSystemToKwin")
idNameMap := getSystemIdNameMap()
Expand All @@ -1493,29 +1520,28 @@ func (sm *ShortcutManager) AddSystemToKwin(wmObj wm.Wm) {
logger.Warning("Failed to convert keystrokes:", err)
continue
}
accelJson, err := util.MarshalJSON(util.KWinAccel{
Id: id,
Keystrokes: keystrokes,
})
keystrokes = NormalizeSystemKeystrokesForKWin(id, keystrokes)

var accelJson string
if id == "screenshotWindow" {
accelJson = `{"Id":"screenshotWindow","Accels":["SysReq"]}` //+ Alt+print对应kwin识别的键SysReq
}
if id == "launcher" {
} else if id == "launcher" {
accelJson = `{"Id":"launcher","Accels":["Super_L"]}`
}
if id == "system_monitor" {
accelJson = `{"Id":"system_monitor","Accels":["<Crtl><Alt>Escape"]}`
}
if err != nil {
logger.Warning("failed to get json:", err)
continue
} else if id == "systemMonitor" {
accelJson = `{"Id":"systemMonitor","Accels":["<Crtl><Alt>Escape"]}`
} else {
accelJson, err = util.MarshalJSON(util.KWinAccel{
Id: id,
Keystrokes: keystrokes,
})
if err != nil {
logger.Warning("failed to get json:", err)
continue
}
}
ok, err := wmObj.SetAccel(0, accelJson)
if !ok {

logKeystrokes, _ := convertToStringSlice(strokesValue.Value())
logger.Warning("failed to set KWin accels:", id, logKeystrokes, err)
logger.Warning("failed to set KWin accels:", id, keystrokes, err)
}
sm.AddSystemById(wmObj, id)
}
Expand Down
Loading