Skip to content

Instantly share code, notes, and snippets.

@dmitriid
Created April 25, 2026 17:01
Show Gist options
  • Select an option

  • Save dmitriid/69d44a62f95d4b685424eea5483cc8e8 to your computer and use it in GitHub Desktop.

Select an option

Save dmitriid/69d44a62f95d4b685424eea5483cc8e8 to your computer and use it in GitHub Desktop.
Create .desktop launchers from URLs, flatpaks, scripts, or executables
#!/usr/bin/env bash
#
# appify - create .desktop launchers from URLs, flatpaks, scripts, or executables
#
# Usage:
# appify <name> <target> [icon-url] CLI mode
# appify Interactive mode (prompts for input)
# appify --remove Pick an app to remove via fzf
#
# Target type is auto-detected:
# https://... -> opens in Firefox
# com.example.App -> flatpak run (verified via flatpak info)
# /path/to/script.sh -> launches in alacritty with its own window class
# /path/to/binary -> runs directly
#
# Icon URL is downloaded and saved to ~/.local/share/icons/appify/
# Desktop files are written to ~/.local/share/applications/
#
# Examples:
# appify "Gmail" "https://mail.google.com" "https://cdn.example.com/gmail.png"
# appify "TablePlus" "com.tinyapp.TablePlus"
# appify "MOP" "/home/user/Projects/mop/mop.sh" "https://example.com/icon.png"
#
set -euo pipefail
ICON_DIR="$HOME/.local/share/icons/appify"
APP_DIR="$HOME/.local/share/applications"
slugify() {
echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//'
}
detect_type() {
local target="$1"
if [[ "$target" =~ ^https?:// ]]; then
echo "url"
elif flatpak info "$target" &>/dev/null; then
echo "flatpak"
elif [[ -x "$target" ]] || [[ -x "$(command -v "$target" 2>/dev/null)" ]]; then
local resolved="${target}"
[[ ! -f "$resolved" ]] && resolved="$(command -v "$target" 2>/dev/null)" || true
local mime
mime="$(file -b --mime-type "$resolved" 2>/dev/null)"
case "$mime" in
text/*|application/x-shellscript) echo "script" ;;
*) echo "exec" ;;
esac
else
echo >&2 "error: '$target' is not a URL, flatpak ID, or executable"
exit 1
fi
}
download_icon() {
local url="$1" slug="$2"
local ext="${url##*.}"
[[ "$ext" =~ ^(png|svg|ico|jpg|jpeg|webp)(\?.*)?$ ]] || ext="png"
ext="${ext%%\?*}"
local dest="$ICON_DIR/${slug}.${ext}"
mkdir -p "$ICON_DIR"
curl -fsSL -o "$dest" "$url"
echo "$dest"
}
create_desktop() {
local name="$1" exec_line="$2" icon="$3" slug="$4" terminal="$5"
local desktop_file="$APP_DIR/${slug}.desktop"
cat > "$desktop_file" <<EOF
[Desktop Entry]
Name=${name}
Exec=${exec_line}
Icon=${icon}
Terminal=false
Type=Application
Categories=Utility;
StartupWMClass=${slug}-app
EOF
chmod +x "$desktop_file"
update-desktop-database "$APP_DIR" 2>/dev/null || true
echo "$desktop_file"
}
build_exec() {
local type="$1" target="$2" slug="$3"
case "$type" in
url) echo "firefox --new-window '${target}'" ;;
flatpak) echo "flatpak run ${target}" ;;
script) echo "alacritty --class ${slug}-app -e ${target}" ;;
exec) echo "${target}" ;;
esac
}
remove_app() {
local files=("$APP_DIR"/*.desktop)
[[ ! -e "${files[0]}" ]] && { echo "No apps found."; exit 0; }
local choice
choice="$(for f in "${files[@]}"; do
name="$(grep -m1 '^Name=' "$f" | cut -d= -f2-)"
echo "$name $f"
done | fzf --with-nth=1 --delimiter=$'\t' --prompt="Remove app: ")" || exit 0
local desktop_file="${choice##*$'\t'}"
local slug="$(basename "$desktop_file" .desktop)"
rm -f "$desktop_file"
rm -f "$ICON_DIR/$slug".*
update-desktop-database "$APP_DIR" 2>/dev/null || true
echo "Removed: $desktop_file"
}
# --- interactive or CLI ---
if [[ "${1:-}" == "--remove" ]]; then
remove_app
exit 0
elif [[ $# -ge 2 ]]; then
name="$1"
target="$2"
icon_url="${3:-}"
else
read -rp "App name: " name
read -rp "Path / URL / Flatpak ID: " target
read -rp "Icon URL (empty to skip): " icon_url
fi
[[ -z "$name" || -z "$target" ]] && { echo >&2 "error: name and target are required"; exit 1; }
slug="$(slugify "$name")"
# resolve relative paths to absolute
if [[ "$target" != http* ]] && [[ -e "$target" ]]; then
target="$(realpath "$target")"
fi
type="$(detect_type "$target")"
if [[ -n "${icon_url:-}" ]]; then
icon="$(download_icon "$icon_url" "$slug")"
else
icon=""
fi
exec_line="$(build_exec "$type" "$target" "$slug")"
desktop_file="$(create_desktop "$name" "$exec_line" "$icon" "$slug" "false")"
echo "Created: $desktop_file"
echo " Type: $type"
echo " Exec: $exec_line"
[[ -n "$icon" ]] && echo " Icon: $icon"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment