diff --git a/keybinding1/manager_handlers.go b/keybinding1/manager_handlers.go index 0d7c79f10..811e506f6 100644 --- a/keybinding1/manager_handlers.go +++ b/keybinding1/manager_handlers.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -6,11 +6,20 @@ package keybinding import ( "fmt" + "os/exec" + "path/filepath" + "strings" "time" "github.com/godbus/dbus/v5" "github.com/linuxdeepin/dde-daemon/keybinding1/constants" . "github.com/linuxdeepin/dde-daemon/keybinding1/shortcuts" + gio "github.com/linuxdeepin/go-gir/gio-2.0" +) + +const ( + gsSchemaDefaultTerminal = "com.deepin.desktop.default-applications.terminal" + gsKeyTerminalAppId = "app-id" ) func (m *Manager) shouldShowCapsLockOSD() bool { @@ -146,6 +155,24 @@ func (m *Manager) initHandlers() { }() } + m.handlers[ActionTypeLaunchMimeType] = func(ev *KeyEvent) { + action := ev.Shortcut.GetAction() + mimeType, ok := action.Arg.(string) + if !ok { + logger.Warning(ErrTypeAssertionFail) + return + } + go m.launchDefaultForMimeType(mimeType) + } + + m.handlers[ActionTypeLaunchTerminal] = func(ev *KeyEvent) { + go m.launchDefaultTerminal() + } + + m.handlers[ActionTypeLockScreen] = func(ev *KeyEvent) { + go m.lockScreen() + } + m.handlers[ActionTypeAudioCtrl] = buildHandlerFromController(m.audioController) m.handlers[ActionTypeMediaPlayerCtrl] = buildHandlerFromController(m.mediaPlayerController) m.handlers[ActionTypeDisplayCtrl] = buildHandlerFromController(m.displayController) @@ -275,3 +302,100 @@ type ErrIsNil struct { func (err ErrIsNil) Error() string { return fmt.Sprintf("%s is nil", err.Name) } + +// launchDefaultForMimeType launches the default application for the given mime type +// via AM in-process, avoiding subprocess spawning. +func (m *Manager) launchDefaultForMimeType(mimeType string) { + appInfo := gio.AppInfoGetDefaultForType(mimeType, false) + if appInfo == nil { + logger.Warning("no default app for mime type:", mimeType) + return + } + defer appInfo.Unref() + + dAppInfo := gio.ToDesktopAppInfo(appInfo) + desktopFile := filepath.Base(dAppInfo.GetFilename()) + + err := m.runDesktopFile(desktopFile) + if err != nil { + logger.Warning("launch mime type error:", err) + } +} + +// launchDefaultTerminal launches the default terminal via AM in-process, +// using GSettings to find the configured terminal application. +func (m *Manager) launchDefaultTerminal() { + settings := gio.NewSettings(gsSchemaDefaultTerminal) + if settings == nil { + logger.Warning("failed to get terminal settings") + return + } + defer settings.Unref() + + appId := settings.GetString(gsKeyTerminalAppId) + err := m.runDesktopFile(appId) + if err != nil { + logger.Warning("launch terminal error:", err) + } +} + +// lockScreen performs X11 grab cleanup and shows the lock screen via +// in-process DBus, avoiding subprocess spawning for dbus-send. +func (m *Manager) lockScreen() { + origOption := m.getCurrentXkbOption() + logger.Debug("lockScreen: orig option:", origOption) + + m.breakXGrab() + defer m.restoreXkbOption(origOption) + + m.showLockFront() +} + +// getCurrentXkbOption returns the current keyboard options from setxkbmap -query. +// Returns empty string if no options are set or an error occurs. +func (m *Manager) getCurrentXkbOption() string { + out, err := exec.Command("setxkbmap", "-query").Output() + if err != nil { + logger.Warning("lockScreen: setxkbmap -query failed:", err) + return "" + } + + for _, line := range strings.Split(string(out), "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "options:") { + // line format: "options: grp:alt_shift_toggle,terminate:ctrl_alt_bksp" + fields := strings.Fields(line) + if len(fields) >= 2 { + return fields[1] + } + } + } + return "" +} + +// breakXGrab sets grab:break_actions and sends XF86Ungrab to break X11 keyboard grabs. +// On Wayland, the xdotool step is skipped (see Bug-224309). +func (m *Manager) breakXGrab() { + exec.Command("setxkbmap", "-option", "grab:break_actions").Run() + if !_useWayland { + exec.Command("xdotool", "key", "XF86Ungrab").Run() + } +} + +// restoreXkbOption restores the previous keyboard options if non-empty. +func (m *Manager) restoreXkbOption(orig string) { + if orig != "" { + exec.Command("setxkbmap", "-option", orig).Run() + } +} + +// showLockFront calls LockFront1.Show via in-process DBus. +func (m *Manager) showLockFront() { + lockObj := m.sessionSigLoop.Conn().Object( + "org.deepin.dde.LockFront1", + "/org/deepin/dde/LockFront1") + err := lockObj.Call("org.deepin.dde.LockFront1.Show", 0).Err + if err != nil { + logger.Warning("lockScreen: failed to show lock front:", err) + } +} diff --git a/keybinding1/shortcuts/action.go b/keybinding1/shortcuts/action.go index dfdd395a8..17789d9a5 100644 --- a/keybinding1/shortcuts/action.go +++ b/keybinding1/shortcuts/action.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -11,6 +11,9 @@ const ( ActionTypeExecCmd ActionTypeOpenMimeType ActionTypeDesktopFile + ActionTypeLaunchMimeType + ActionTypeLaunchTerminal + ActionTypeLockScreen ActionTypeShowNumLockOSD ActionTypeShowCapsLockOSD ActionTypeSystemShutdown @@ -69,6 +72,28 @@ func NewOpenMimeTypeAction(mimeType string) *Action { } } +// launch the default application for the given mime type via AM (in-process) +func NewLaunchMimeTypeAction(mimeType string) *Action { + return &Action{ + Type: ActionTypeLaunchMimeType, + Arg: mimeType, + } +} + +// launch the default terminal via AM (in-process) using GSettings +func NewLaunchTerminalAction() *Action { + return &Action{ + Type: ActionTypeLaunchTerminal, + } +} + +// lock screen, X11 grab cleanup + in-process DBus (no dbus-send subprocess) +func NewLockScreenAction() *Action { + return &Action{ + Type: ActionTypeLockScreen, + } +} + type ActionCmd uint const ( diff --git a/keybinding1/shortcuts/shortcut_manager.go b/keybinding1/shortcuts/shortcut_manager.go index 0a266d981..5158c8839 100644 --- a/keybinding1/shortcuts/shortcut_manager.go +++ b/keybinding1/shortcuts/shortcut_manager.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -1192,6 +1192,18 @@ func (sm *ShortcutManager) AddSystemById(wmObj wm.Wm, id string) { Cmd: cmd, }, } + // fileManager 走 in-process AM Launch,不 spawn 子进程 + if id == "fileManager" { + sysShortcut.customAction = NewLaunchMimeTypeAction("inode/directory") + } + // terminal 走 in-process AM Launch,不 spawn 子进程 + if id == "terminal" { + sysShortcut.customAction = NewLaunchTerminalAction() + } + // lockScreen 走 in-process DBus,不 spawn dbus-send + if id == "lockScreen" || id == "lockScreen-wayland" { + sysShortcut.customAction = NewLockScreenAction() + } sm.addWithoutLock(sysShortcut) } diff --git a/keybinding1/shortcuts/system_shortcut.go b/keybinding1/shortcuts/system_shortcut.go index 032be7bcf..1bc91fdde 100644 --- a/keybinding1/shortcuts/system_shortcut.go +++ b/keybinding1/shortcuts/system_shortcut.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -22,7 +22,8 @@ const ( type SystemShortcut struct { *ShortcutObject - arg *ActionExecCmdArg + arg *ActionExecCmdArg + customAction *Action } func (ss *SystemShortcut) SetName(name string) error { @@ -30,6 +31,9 @@ func (ss *SystemShortcut) SetName(name string) error { } func (ss *SystemShortcut) GetAction() *Action { + if ss.customAction != nil { + return ss.customAction + } return &Action{ Type: ActionTypeExecCmd, Arg: ss.arg, @@ -40,15 +44,16 @@ func (ss *SystemShortcut) SetAction(newAction *Action) error { if newAction == nil { return ErrNilAction } - if newAction.Type != ActionTypeExecCmd { - return ErrInvalidActionType - } - - arg, ok := newAction.Arg.(*ActionExecCmdArg) - if !ok { - return ErrTypeAssertionFail + if newAction.Type == ActionTypeExecCmd { + arg, ok := newAction.Arg.(*ActionExecCmdArg) + if !ok { + return ErrTypeAssertionFail + } + ss.arg = arg + ss.customAction = nil + } else { + ss.customAction = newAction } - ss.arg = arg return nil }