Add launcher uninstall confirmation

This commit is contained in:
David Heinemeier Hansson
2026-05-25 18:32:41 +02:00
parent 45e9322d2a
commit 32b642e085
3 changed files with 289 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
#!/bin/bash
# omarchy:summary=Remove or uninstall the selected launcher entry
# omarchy:args=<desktop-id> <name>
# omarchy:hidden=true
# omarchy:requires-sudo=true
set -e
desktop_id="${1-}"
entry_name="${2-}"
if [[ -z $desktop_id ]]; then
echo "Usage: omarchy-remove-launcher-entry <desktop-id> <name>" >&2
exit 1
fi
desktop_file_name="$desktop_id"
if [[ $desktop_file_name != *.desktop ]]; then
desktop_file_name="$desktop_file_name.desktop"
fi
desktop_dirs=()
if [[ -n ${XDG_DATA_HOME-} ]]; then
desktop_dirs+=("$XDG_DATA_HOME/applications")
else
desktop_dirs+=("$HOME/.local/share/applications")
fi
if [[ -n ${XDG_DATA_DIRS-} ]]; then
IFS=: read -ra data_dirs <<<"$XDG_DATA_DIRS"
for data_dir in "${data_dirs[@]}"; do
desktop_dirs+=("$data_dir/applications")
done
else
desktop_dirs+=("/usr/local/share/applications" "/usr/share/applications")
fi
desktop_file=""
for dir in "${desktop_dirs[@]}"; do
candidate="$dir/$desktop_file_name"
if [[ -f $candidate ]]; then
desktop_file="$candidate"
break
fi
done
if [[ -z $desktop_file ]]; then
echo "Could not find launcher entry: $desktop_file_name" >&2
exit 1
fi
desktop_name="${desktop_file##*/}"
desktop_name="${desktop_name%.desktop}"
exec_line="$(sed -n 's/^Exec=//p' "$desktop_file" | head -1)"
if [[ $exec_line =~ omarchy-launch-webapp|omarchy-webapp-handler ]]; then
omarchy-webapp-remove "$desktop_name"
exit 0
fi
if [[ $exec_line =~ (^|[[:space:]])(\$TERMINAL|xdg-terminal-exec)[[:space:]].*-e([[:space:]]|$) ]]; then
omarchy-tui-remove "$desktop_name"
exit 0
fi
if package_name="$(pacman -Qqo "$desktop_file" 2>/dev/null | head -1)" && [[ -n $package_name ]]; then
display_name="${entry_name:-$desktop_name}"
quoted_package="$(printf '%q' "$package_name")"
quoted_display="$(printf '%q' "$display_name")"
exec omarchy-launch-floating-terminal-with-presentation "echo Uninstalling $quoted_display...; sudo pacman -Rns $quoted_package"
fi
if command -v flatpak >/dev/null 2>&1 && flatpak info "${desktop_file_name%.desktop}" >/dev/null 2>&1; then
flatpak_id="${desktop_file_name%.desktop}"
quoted_flatpak="$(printf '%q' "$flatpak_id")"
exec omarchy-launch-floating-terminal-with-presentation "flatpak uninstall $quoted_flatpak"
fi
echo "Don't know how to uninstall $desktop_file_name" >&2
exit 1
+137
View File
@@ -27,6 +27,9 @@ Item {
property string launchOsdMessage: ""
property var configuredHiddenEntryIds: ({})
property var desktopHiddenEntryIds: ({})
property bool deleteConfirmOpen: false
property var deleteEntry: null
property int deleteConfirmIndex: 1
// Bound to the central [launcher] section in shell.toml via Color.qml.
// Each color already includes its alpha companion (composed in the
@@ -75,6 +78,7 @@ Item {
}
function dismiss() {
root.deleteConfirmOpen = false
root.opened = false
if (root.shell && typeof root.shell.hide === "function")
root.shell.hide((root.manifest && root.manifest.id) || "omarchy.launcher")
@@ -196,6 +200,7 @@ Item {
}
function activateIndex(index) {
if (root.deleteConfirmOpen) return
if (index < 0 || index >= root.filteredEntries.length) return
var entry = root.filteredEntries[index]
if (!entry) return
@@ -204,6 +209,33 @@ Item {
entry.execute()
}
function requestDeleteIndex(index) {
if (index < 0 || index >= root.filteredEntries.length) return
var entry = root.filteredEntries[index]
if (!entry) return
root.deleteEntry = entry
root.deleteConfirmIndex = 1
root.deleteConfirmOpen = true
}
function cancelDelete() {
root.deleteConfirmOpen = false
root.deleteEntry = null
root.deleteConfirmIndex = 1
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
}
function confirmDelete() {
var entry = root.deleteEntry
if (!entry) return
var desktopId = String(entry.id || "")
var name = root.entryName(entry)
root.cancelDelete()
root.dismiss()
Quickshell.execDetached([root.omarchyPath + "/bin/omarchy-remove-launcher-entry", desktopId, name])
}
function beginLaunchFeedback(entry) {
root.launchSerial++
root.launchToplevelCount = root.toplevelCount()
@@ -327,10 +359,26 @@ Item {
Item {
id: keyCatcher
anchors.fill: parent
z: root.deleteConfirmOpen ? 20 : 0
focus: true
Keys.priority: Keys.BeforeItem
Keys.onPressed: function(event) {
if (root.deleteConfirmOpen) {
if (event.key === Qt.Key_Escape) {
root.cancelDelete()
event.accepted = true
} else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab) {
root.deleteConfirmIndex = root.deleteConfirmIndex === 0 ? 1 : 0
event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (root.deleteConfirmIndex === 0) root.cancelDelete()
else root.confirmDelete()
event.accepted = true
}
return
}
if (event.key === Qt.Key_Escape) {
if (root.filterText.length > 0) root.setFilter("")
else root.dismiss()
@@ -367,11 +415,100 @@ Item {
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
root.activateIndex(root.selectedIndex)
event.accepted = true
} else if (event.key === Qt.Key_Delete) {
root.requestDeleteIndex(root.selectedIndex)
event.accepted = true
} else if (event.text && event.text.length === 1 && event.text.charCodeAt(0) >= 32 && event.text.charCodeAt(0) !== 127 && (event.modifiers === Qt.NoModifier || event.modifiers === Qt.ShiftModifier)) {
root.setFilter(root.filterText + event.text)
event.accepted = true
}
}
Rectangle {
anchors.fill: parent
visible: root.deleteConfirmOpen
z: 10
color: root.scrim
MouseArea { anchors.fill: parent; onClicked: root.cancelDelete() }
Rectangle {
width: Math.min(parent.width - 96, 370)
height: 132
anchors.centerIn: parent
color: root.background
border.color: root.selectedText
border.width: 1
radius: Style.cornerRadius
MouseArea { anchors.fill: parent; onClicked: {} }
Item {
anchors.fill: parent
anchors.margins: 18
Text {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
text: "Do you want to uninstall " + root.entryName(root.deleteEntry) + "?"
color: root.foreground
font.family: root.fontFamily
font.pixelSize: 16
wrapMode: Text.WordWrap
}
Row {
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: 10
Repeater {
model: ["Cancel", "Uninstall"]
Rectangle {
required property int index
required property string modelData
readonly property bool selected: root.deleteConfirmIndex === index
readonly property bool destructive: index === 1
width: 88
height: 34
color: selected
? (destructive ? Util.alpha(Color.urgent, 0.22) : root.selectedBackground)
: "transparent"
border.color: destructive
? (selected ? Color.urgent : Util.alpha(Color.urgent, 0.56))
: (selected ? root.selectedText : Util.alpha(root.foreground, 0.38))
border.width: 1
radius: 0
Text {
id: buttonText
anchors.centerIn: parent
text: modelData
color: destructive ? (selected ? Color.urgent : root.foreground) : (selected ? root.selectedText : root.foreground)
font.family: root.fontFamily
font.pixelSize: 14
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: root.deleteConfirmIndex = index
onClicked: {
if (index === 0) root.cancelDelete()
else root.confirmDelete()
}
}
}
}
}
}
}
}
}
Column {
+71
View File
@@ -0,0 +1,71 @@
#!/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
mkdir -p "$tmp_dir/data/applications" "$tmp_dir/bin"
write_fake_command() {
local name="$1"
local prefix="$2"
cat >"$tmp_dir/bin/$name" <<SCRIPT
#!/bin/bash
printf '%s:%s\\n' '$prefix' "\$*" >>"\$TEST_LOG"
SCRIPT
chmod +x "$tmp_dir/bin/$name"
}
write_fake_command omarchy-webapp-remove web
write_fake_command omarchy-tui-remove tui
write_fake_command omarchy-launch-floating-terminal-with-presentation terminal
cat >"$tmp_dir/bin/pacman" <<'SCRIPT'
#!/bin/bash
if [[ $1 == "-Qqo" ]]; then
printf 'native-pkg\n'
fi
SCRIPT
chmod +x "$tmp_dir/bin/pacman"
cat >"$tmp_dir/data/applications/Basecamp.desktop" <<'DESKTOP'
[Desktop Entry]
Name=Basecamp
Exec=omarchy-launch-webapp https://example.com
DESKTOP
cat >"$tmp_dir/data/applications/Docker.desktop" <<'DESKTOP'
[Desktop Entry]
Name=Docker
Exec=xdg-terminal-exec --app-id=TUI.tile -e lazydocker
DESKTOP
cat >"$tmp_dir/data/applications/native.desktop" <<'DESKTOP'
[Desktop Entry]
Name=Native
Exec=native
DESKTOP
export TEST_LOG="$tmp_dir/log"
export PATH="$tmp_dir/bin:$PATH"
export XDG_DATA_HOME="$tmp_dir/data"
export XDG_DATA_DIRS=
"$ROOT/bin/omarchy-remove-launcher-entry" Basecamp.desktop Basecamp
"$ROOT/bin/omarchy-remove-launcher-entry" Docker.desktop Docker
"$ROOT/bin/omarchy-remove-launcher-entry" native.desktop Native
mapfile -t lines <"$TEST_LOG"
[[ ${lines[0]} == "web:Basecamp" ]] || fail "launcher remove routes web apps by desktop name" "${lines[0]}"
pass "launcher remove routes web apps by desktop name"
[[ ${lines[1]} == "tui:Docker" ]] || fail "launcher remove routes TUIs by desktop name" "${lines[1]}"
pass "launcher remove routes TUIs by desktop name"
[[ ${lines[2]} == "terminal:echo Uninstalling Native...; sudo pacman -Rns native-pkg" ]] || fail "launcher remove opens package uninstall flow" "${lines[2]}"
pass "launcher remove opens package uninstall flow"