From 987addd7dac070b0023e8e296910040f8abbea24 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 29 Jun 2026 11:14:07 -0500 Subject: [PATCH] Launch presentation terminals on active workspace --- ...launch-floating-terminal-with-presentation | 38 ++++++++++++++- shell/plugins/launcher/Launcher.qml | 13 +++++- test/shell.d/floating-terminal-test.sh | 46 +++++++++++++++++++ test/shell.d/launcher-search-test.sh | 8 ++++ 4 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 test/shell.d/floating-terminal-test.sh diff --git a/bin/omarchy-launch-floating-terminal-with-presentation b/bin/omarchy-launch-floating-terminal-with-presentation index b0eaee72..d280d5b4 100755 --- a/bin/omarchy-launch-floating-terminal-with-presentation +++ b/bin/omarchy-launch-floating-terminal-with-presentation @@ -4,4 +4,40 @@ # omarchy:args= cmd="$*" -exec setsid uwsm-app -- xdg-terminal-exec --app-id=org.omarchy.terminal --title=Omarchy -e bash -c "omarchy-show-logo; $cmd; if (( \$? != 130 )); then omarchy-show-done; fi" +presentation_script="omarchy-show-logo; $cmd; if (( \$? != 130 )); then omarchy-show-done; fi" + +shell_quote() { + local value=$1 + + printf "'" + printf "%s" "$value" | sed "s/'/'\\\\''/g" + printf "'" +} + +lua_quote() { + local value=$1 + + value=${value//\\/\\\\} + value=${value//\"/\\\"} + value=${value//$'\n'/\\n} + printf '"%s"' "$value" +} + +active_workspace() { + hyprctl activeworkspace -j 2>/dev/null | jq -r '.name // .id // empty' 2>/dev/null +} + +terminal_command="setsid uwsm-app -- xdg-terminal-exec --app-id=org.omarchy.terminal --title=Omarchy -e bash -c $(shell_quote "$presentation_script")" +workspace="${OMARCHY_LAUNCH_WORKSPACE:-}" + +if [[ -z $workspace ]] && [[ -n ${HYPRLAND_INSTANCE_SIGNATURE:-} ]] && omarchy-cmd-present hyprctl && omarchy-cmd-present jq; then + workspace="$(active_workspace)" +fi + +if [[ -n $workspace ]] && [[ -n ${HYPRLAND_INSTANCE_SIGNATURE:-} ]] && omarchy-cmd-present hyprctl; then + if hyprctl dispatch "function() hl.exec_cmd($(lua_quote "$terminal_command"), { workspace = $(lua_quote "$workspace") }) end" >/dev/null 2>&1; then + exit 0 + fi +fi + +exec setsid uwsm-app -- xdg-terminal-exec --app-id=org.omarchy.terminal --title=Omarchy -e bash -c "$presentation_script" diff --git a/shell/plugins/launcher/Launcher.qml b/shell/plugins/launcher/Launcher.qml index 14fbaf50..8ef18a4d 100644 --- a/shell/plugins/launcher/Launcher.qml +++ b/shell/plugins/launcher/Launcher.qml @@ -1,4 +1,5 @@ import Quickshell +import Quickshell.Hyprland import Quickshell.Io import Quickshell.Wayland import Quickshell.Widgets @@ -24,6 +25,7 @@ Item { property int launchSerial: 0 property int launchToplevelCount: 0 property var launchActiveToplevel: null + property string launchWorkspace: "" property bool launchOsdOpen: false property string launchOsdMessage: "" property var configuredHiddenEntryIds: ({}) @@ -75,6 +77,7 @@ Item { ? root.contentMargin * 2 + root.searchHeight + root.contentSpacing + requestedListHeight : 400 + root.launchWorkspace = String(payload.workspace || "") || root.focusedWorkspaceName() root.opened = true root.filterText = payload.query || "" root.selectedIndex = 0 @@ -129,6 +132,12 @@ Item { return LauncherSearch.entrySearchText(entry) } + function focusedWorkspaceName() { + var workspace = Hyprland.focusedWorkspace + if (!workspace) return "" + return String(workspace.name || workspace.id || "") + } + function isHiddenEntry(entry) { var id = String((entry && entry.id) || "") return root.configuredHiddenEntryIds[id] === true || root.desktopHiddenEntryIds[id] === true @@ -277,8 +286,10 @@ Item { var desktopId = String(entry.id || "") var name = root.entryName(entry) + var command = [root.omarchyPath + "/bin/omarchy-remove-launcher-entry", desktopId, name] + if (root.launchWorkspace !== "") command = ["env", "OMARCHY_LAUNCH_WORKSPACE=" + root.launchWorkspace].concat(command) root.cancelDelete() - Quickshell.execDetached([root.omarchyPath + "/bin/omarchy-remove-launcher-entry", desktopId, name]) + Quickshell.execDetached(command) } function beginLaunchFeedback(entry) { diff --git a/test/shell.d/floating-terminal-test.sh b/test/shell.d/floating-terminal-test.sh new file mode 100644 index 00000000..a563ee40 --- /dev/null +++ b/test/shell.d/floating-terminal-test.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +tmp_dir="$(mktemp -d)" +trap 'rm -rf "$tmp_dir"' EXIT + +cat >"$tmp_dir/hyprctl" <<'SCRIPT' +#!/bin/bash +case "$1" in + activeworkspace) + printf '{"id":2,"name":"2"}\n' + ;; + dispatch) + printf '%s\n' "$2" >>"$TEST_LOG" + ;; +esac +SCRIPT +chmod +x "$tmp_dir/hyprctl" + +cat >"$tmp_dir/jq" <<'SCRIPT' +#!/bin/bash +cat >/dev/null +printf '2\n' +SCRIPT +chmod +x "$tmp_dir/jq" + +export TEST_LOG="$tmp_dir/log" +export PATH="$tmp_dir:$ROOT/bin:$PATH" +export HYPRLAND_INSTANCE_SIGNATURE=test + +"$ROOT/bin/omarchy-launch-floating-terminal-with-presentation" "echo hello" + +dispatch=$(<"$TEST_LOG") +[[ $dispatch == *'workspace = "2"'* ]] || fail "floating terminal targets active workspace" "$dispatch" +[[ $dispatch == *"xdg-terminal-exec --app-id=org.omarchy.terminal"* ]] || fail "floating terminal dispatch launches Omarchy terminal" "$dispatch" +pass "floating terminal targets active workspace" + +: >"$TEST_LOG" +OMARCHY_LAUNCH_WORKSPACE=7 "$ROOT/bin/omarchy-launch-floating-terminal-with-presentation" "echo hello" + +dispatch=$(<"$TEST_LOG") +[[ $dispatch == *'workspace = "7"'* ]] || fail "floating terminal honors explicit launch workspace" "$dispatch" +pass "floating terminal honors explicit launch workspace" diff --git a/test/shell.d/launcher-search-test.sh b/test/shell.d/launcher-search-test.sh index 13d41c49..7e74f40c 100644 --- a/test/shell.d/launcher-search-test.sh +++ b/test/shell.d/launcher-search-test.sh @@ -79,4 +79,12 @@ assert( !confirmDeleteMatch[1].includes('root.dismiss()'), 'launcher delete keeps launcher open after confirmation' ) +assert( + confirmDeleteMatch[1].includes('OMARCHY_LAUNCH_WORKSPACE='), + 'launcher delete passes launch workspace to remover' +) +assert( + launcherQml.includes('function focusedWorkspaceName()'), + 'launcher captures focused workspace' +) JS