Add launcher uninstall confirmation
This commit is contained in:
Executable
+81
@@ -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
|
||||||
@@ -27,6 +27,9 @@ Item {
|
|||||||
property string launchOsdMessage: ""
|
property string launchOsdMessage: ""
|
||||||
property var configuredHiddenEntryIds: ({})
|
property var configuredHiddenEntryIds: ({})
|
||||||
property var desktopHiddenEntryIds: ({})
|
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.
|
// Bound to the central [launcher] section in shell.toml via Color.qml.
|
||||||
// Each color already includes its alpha companion (composed in the
|
// Each color already includes its alpha companion (composed in the
|
||||||
@@ -75,6 +78,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dismiss() {
|
function dismiss() {
|
||||||
|
root.deleteConfirmOpen = false
|
||||||
root.opened = false
|
root.opened = false
|
||||||
if (root.shell && typeof root.shell.hide === "function")
|
if (root.shell && typeof root.shell.hide === "function")
|
||||||
root.shell.hide((root.manifest && root.manifest.id) || "omarchy.launcher")
|
root.shell.hide((root.manifest && root.manifest.id) || "omarchy.launcher")
|
||||||
@@ -196,6 +200,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function activateIndex(index) {
|
function activateIndex(index) {
|
||||||
|
if (root.deleteConfirmOpen) return
|
||||||
if (index < 0 || index >= root.filteredEntries.length) return
|
if (index < 0 || index >= root.filteredEntries.length) return
|
||||||
var entry = root.filteredEntries[index]
|
var entry = root.filteredEntries[index]
|
||||||
if (!entry) return
|
if (!entry) return
|
||||||
@@ -204,6 +209,33 @@ Item {
|
|||||||
entry.execute()
|
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) {
|
function beginLaunchFeedback(entry) {
|
||||||
root.launchSerial++
|
root.launchSerial++
|
||||||
root.launchToplevelCount = root.toplevelCount()
|
root.launchToplevelCount = root.toplevelCount()
|
||||||
@@ -327,10 +359,26 @@ Item {
|
|||||||
Item {
|
Item {
|
||||||
id: keyCatcher
|
id: keyCatcher
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
z: root.deleteConfirmOpen ? 20 : 0
|
||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
Keys.priority: Keys.BeforeItem
|
Keys.priority: Keys.BeforeItem
|
||||||
Keys.onPressed: function(event) {
|
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 (event.key === Qt.Key_Escape) {
|
||||||
if (root.filterText.length > 0) root.setFilter("")
|
if (root.filterText.length > 0) root.setFilter("")
|
||||||
else root.dismiss()
|
else root.dismiss()
|
||||||
@@ -367,11 +415,100 @@ Item {
|
|||||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||||
root.activateIndex(root.selectedIndex)
|
root.activateIndex(root.selectedIndex)
|
||||||
event.accepted = true
|
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)) {
|
} 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)
|
root.setFilter(root.filterText + event.text)
|
||||||
event.accepted = true
|
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 {
|
Column {
|
||||||
|
|||||||
Executable
+71
@@ -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"
|
||||||
Reference in New Issue
Block a user