diff --git a/assets/translations/en.json b/assets/translations/en.json index b97e31c88b..d8594d9aa3 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1988,6 +1988,11 @@ "description": "Launches applications in separate systemd services so they don't share Noctalia's cgroup", "label": "Launch Apps as systemd Services" }, + "launch-apps-custom-command": { + "description": "Custom command used by the app launcher, it substitutes $CMD with the original command.", + "label": "Custom Launcher Command", + "placeholder": "$CMD" + }, "middle-click-opens-widget-settings": { "description": "Open a bar widget's settings from the bar with middle click", "label": "Middle Click Opens Widget Settings" diff --git a/src/config/config_types.h b/src/config/config_types.h index 067496dc00..36671c58c1 100644 --- a/src/config/config_types.h +++ b/src/config/config_types.h @@ -805,6 +805,7 @@ struct ShellConfig { bool appIconColorize = false; std::optional appIconColor; bool launchAppsAsSystemdServices = false; + std::string launchAppsCustomCommand; /// When false, disables Wayland clipboard integration (history panel, data-control binding, Input paste/copy hooks). bool clipboardEnabled = true; /// Maximum unpinned clipboard history entries retained (pinned entries are exempt). diff --git a/src/config/schema/config_schema.cpp b/src/config/schema/config_schema.cpp index a84525695e..d64451721c 100644 --- a/src/config/schema/config_schema.cpp +++ b/src/config/schema/config_schema.cpp @@ -1112,6 +1112,7 @@ namespace noctalia::config::schema { field(&ShellConfig::appIconColorize, "app_icon_colorize"), colorSpecField(&ShellConfig::appIconColor, "app_icon_color", /*alwaysEmit=*/false), field(&ShellConfig::launchAppsAsSystemdServices, "launch_apps_as_systemd_services"), + field(&ShellConfig::launchAppsCustomCommand, "launch_apps_custom_command"), field(&ShellConfig::clipboardEnabled, "clipboard_enabled"), field( &ShellConfig::clipboardHistoryMaxEntries, "clipboard_history_max_entries", kClipboardHistoryMaxEntriesRange diff --git a/src/launcher/app_provider.cpp b/src/launcher/app_provider.cpp index 5da6a7feeb..377df128d9 100644 --- a/src/launcher/app_provider.cpp +++ b/src/launcher/app_provider.cpp @@ -238,6 +238,7 @@ bool AppProvider::activate(const LauncherResult& result) { desktop_entry_launch::LaunchOptions launchOptions{ .activationToken = std::move(token), .runAsSystemdService = m_config->config().shell.launchAppsAsSystemdServices, + .customCommand = m_config->config().shell.launchAppsCustomCommand, }; if (chosen != nullptr) { diff --git a/src/shell/bar/widgets/taskbar_widget.cpp b/src/shell/bar/widgets/taskbar_widget.cpp index 8b97fe5368..8030764972 100644 --- a/src/shell/bar/widgets/taskbar_widget.cpp +++ b/src/shell/bar/widgets/taskbar_widget.cpp @@ -1857,6 +1857,7 @@ void TaskbarWidget::openTaskContextMenu(const TaskModel& task, InputArea& area) desktop_entry_launch::LaunchOptions{ .activationToken = std::move(token), .runAsSystemdService = configService.config().shell.launchAppsAsSystemdServices, + .customCommand = configService.config().shell.launchAppsCustomCommand, } ); }); diff --git a/src/shell/dock/dock.cpp b/src/shell/dock/dock.cpp index 2bc22150e4..ffbb15dbfd 100644 --- a/src/shell/dock/dock.cpp +++ b/src/shell/dock/dock.cpp @@ -65,6 +65,7 @@ namespace { return desktop_entry_launch::LaunchOptions{ .activationToken = std::move(token), .runAsSystemdService = config.config().shell.launchAppsAsSystemdServices, + .customCommand = config.config().shell.launchAppsCustomCommand, }; } diff --git a/src/shell/settings/settings_registry.cpp b/src/shell/settings/settings_registry.cpp index 6b5fd26e97..c21f532379 100644 --- a/src/shell/settings/settings_registry.cpp +++ b/src/shell/settings/settings_registry.cpp @@ -1217,6 +1217,17 @@ namespace settings { {"shell", "launch_apps_as_systemd_services"}, ToggleSetting{cfg.shell.launchAppsAsSystemdServices} )); } + entries.push_back(makeEntry( + SettingsSection::Shell, "general", tr("settings.schema.shell.launch-apps-custom-command.label"), + tr("settings.schema.shell.launch-apps-custom-command.description"), {"shell", "launch_apps_custom_command"}, + TextSetting{ + .value = cfg.shell.launchAppsCustomCommand, + .placeholder = tr("settings.schema.shell.launch-apps-custom-command.placeholder"), + .width = 320.0f, + .browseFileExtensions = {}, + }, + "app command custom launcher" + )); const SettingVisibility clipboardOn{{"shell", "clipboard_enabled"}, {"true"}}; entries.push_back(makeEntry( SettingsSection::Shell, "clipboard", tr("settings.schema.shell.clipboard-enabled.label"), diff --git a/src/system/desktop_entry_launch.cpp b/src/system/desktop_entry_launch.cpp index 4ff222845b..38e8c36e2e 100644 --- a/src/system/desktop_entry_launch.cpp +++ b/src/system/desktop_entry_launch.cpp @@ -124,8 +124,25 @@ namespace desktop_entry_launch { return PreparedCommand{std::move(args)}; } + std::string parseCustomCommand(const std::string& exec, const std::string& customCommand) { + std::string command; + if (!customCommand.empty()) { + command = customCommand; + size_t pos = 0; + while ((pos = command.find("$CMD", pos)) != std::string::npos) { + command.replace(pos, 4, exec); + pos += exec.length(); + } + } else { + command = exec; + } + return command; + } + bool launchEntry(const DesktopEntry& entry, const LaunchOptions& options) { - auto prepared = prepareCommand(entry.exec, entry.terminal); + const std::string command = parseCustomCommand(entry.exec, options.customCommand); + auto prepared = prepareCommand(command, entry.terminal); + if (!prepared.has_value()) { kLog.warn("Failed to prepare launch command for desktop entry '{}'", entry.id.empty() ? entry.name : entry.id); return false; @@ -142,7 +159,8 @@ namespace desktop_entry_launch { const DesktopAction& action, std::string_view appName, std::string_view workingDir, bool terminal, const LaunchOptions& options ) { - auto prepared = prepareCommand(action.exec, terminal); + const std::string command = parseCustomCommand(action.exec, options.customCommand); + auto prepared = prepareCommand(command, terminal); if (!prepared.has_value()) { kLog.warn( "Failed to prepare launch command for desktop action '{}'", action.id.empty() ? action.name : action.id diff --git a/src/system/desktop_entry_launch.h b/src/system/desktop_entry_launch.h index 7cf08aeff7..e2258b0584 100644 --- a/src/system/desktop_entry_launch.h +++ b/src/system/desktop_entry_launch.h @@ -12,6 +12,7 @@ namespace desktop_entry_launch { struct LaunchOptions { std::string activationToken; bool runAsSystemdService = false; + std::string customCommand; }; struct PrepareOptions {