Created
April 25, 2026 17:01
-
-
Save dmitriid/69d44a62f95d4b685424eea5483cc8e8 to your computer and use it in GitHub Desktop.
Create .desktop launchers from URLs, flatpaks, scripts, or executables
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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