#!/usr/bin/env bash # Adapted from https://github.com/basecamp/omarchy/tree/master?tab=readme-ov-file THIS_FILE=${BASH_SOURCE[0]} THIS=$(basename "$THIS_FILE") function usage() { cat <: The name of the application. -u | --url : The url used to launch the application. -i | --icon : The icon for the application. -e | --exec : Custom execution command (optional). -m | --mime-types : MIME-types for the application (optional). -f | --file : Install from a spec in a json file. -l | --launch: Launches in a new terminal window in interactive mode. --no-interactive: Don't proceed to interactive mode. If required properties aren't set, then error. This is useful if using '--file' and there are parsing errors or missing properties. --dry-run: Run in dry-run mode, which doesn't generate files or download icons. -h | --help: Show usage information. EXAMPLES: If no options or arguments are supplied, then it will start an interactive session that prompts for the values. $ $THIS Calling the app with named arguments: $ $THIS \\ --name "My Awesome App" \\ --url "https://awesome.com" \\ --icon "https://awesome.com/assets/icon.png" Using a json file as input: $ $THIS --file myapp.json NOTES: The icon option can either be a url where we will download a png from or a local file. Local files can either be the full path to the file or a file name of an icon located in '~/.local/share/applications/icons/'. Interactive sessions do not give the option to use a custom execution command or supply the MIME types, which are less frequently used options. If using a json spec file, all keys are the same as their option name, except for mime-types use 'mime_types' as the key in the json object. Although the 'exec' and 'mime_types' are not required in the spec file. A common json spec file example would look like: { "name": "My Awesome App", "url": "https://awesome.com", "icon: "https://awesome.com/assets/icon.png" } EOF } window_class="com.ghostty.install-webapp" window_padding_x="2" app_name="" app_url="" exec_cmd="" dry_run="0" file_mode_flag="0" icon_ref="" json_file="" mime_types="" launch_flag="0" interactive_flag="0" # This is an internal flag, to not log some things when launch is used. interactive_mode=false no_interactive_flag="0" SCRIPTS="${SCRIPTS:-$HOME/.local/scripts}" XDG_DATA_HOME=${XDG_DATA_HOME} while [[ $# -gt 0 ]]; do if [[ $1 == "-h" ]] || [[ $1 == "--help" ]]; then usage && exit 0 elif [[ $1 == "-n" ]] || [[ $1 == "--name" ]]; then shift app_name=$1 elif [[ $1 == "-u" ]] || [[ $1 == "--url" ]]; then shift app_url=$1 elif [[ $1 == "-i" ]] || [[ $1 == "--icon" ]]; then shift icon_ref=$1 elif [[ $1 == "-e" ]] || [[ $1 == "--exec" ]]; then shift exec_cmd=$1 elif [[ $1 == "-m" ]] || [[ $1 == "--mime-types" ]]; then shift mime_types=$1 elif [[ $1 == "-f" ]] || [[ $1 == "--file" ]]; then file_mode_flag="1" shift json_file=$1 elif [[ $1 == "-l" ]] || [[ $1 == "--launch" ]]; then launch_flag="1" elif [[ $1 == "--interactive" ]]; then interactive_flag="1" elif [[ $1 == "--no-interactive" ]]; then no_interactive_flag="1" elif [[ $1 =~ ^--dry ]]; then dry_run="1" fi shift done log() { logging log --source "$THIS_FILE" "$@" } launch() { ghostty --class=$window_class --window-padding-x=$window_padding_x \ --keybind="ctrl+c=quit" \ -e "${BASH_SOURCE[0]}" --interactive } check_properties() { if [[ -z $app_name ]] || [[ -z $app_url ]] || [[ -z $icon_ref ]]; then return 1 fi return 0 } load_from_file() { if [[ ! -f $1 ]]; then log --error "File '$1' is not found or readable." && exit 1 fi file=$(cat $1) app_name=$(echo $file | jq -r '.name // ""') app_url=$(echo $file | jq -r '.url // ""') icon_ref=$(echo $file | jq -r '.icon // ""') exec_cmd=$(echo $file | jq -r '.exec // ""') mime_types=$(echo $file | jq -r '.mime_types // ""') } print_properties() { log "\e[33mAPP NAME:\e[0m $app_name" log "\e[33mURL:\e[0m $app_url" log "\e[33mICON:\e[0m $icon_ref" log "\e[33mEXEC:\e[0m $exec_cmd" log "\e[33mMIME:\e[0m $mime_types" } prompt_for_properties() { log "\e[32mLet's create a new web app you can start with the app launcher.\n\e[0m" if [[ -z $app_name ]]; then app_name=$(gum input --prompt "Name> " --placeholder "My favorite web app") fi if [[ -z $app_url ]]; then app_url=$(gum input --prompt "URL> " --placeholder "https://example.com") fi if [[ -z $icon_ref ]]; then icon_ref=$(gum input --prompt "Icon URL> " --placeholder "See https://dashboardicons.com (must use PNG!)") fi } set_icon_ref() { # Refer to local icon or fetch remotely from URL local icon_dir="$XDG_DATA_HOME/applications/icons" local icon_path="" # Ensure the icon directory exists (useful if it's the first run.) [ ! -d $icon_dir ] && mkdir -p $icon_dir if [[ $icon_ref == https://* ]]; then icon_path="$icon_dir/$app_name.png" log "Downloading icon: $icon_ref" if [[ $dry_run == "0" ]]; then if curl -sL -o "$icon_path" "$icon_ref"; then icon_path="$icon_dir/$app_name.png" else log --error "Failed to download icon." && exit 1 fi fi else # Check if the icon path is a file. if [ -f $icon_ref ]; then icon_path=$icon_ref else icon_path="$icon_dir/$icon_ref" fi fi icon_ref=$icon_path } generate_file() { cat >"$1" <>"$1" fi chmod +x "$1" } ################################################################################ # MAIN ################################################################################ # Setup logging file and label source "$SCRIPTS/hypr/logging" setup-logging "/tmp/$THIS.log" "$THIS" export LOG_ENABLE_DRY_RUN="$dry_run" if [[ -z "$XDG_DATA_HOME" ]]; then log "XDG_DATA_HOME not set" log "using ~/.local/share" XDG_DATA_HOME=$HOME/.local/share fi if [[ $launch_flag == "1" ]]; then launch && exit 0 fi if [[ $file_mode_flag == "1" ]]; then load_from_file $json_file fi # Check that all properties are set, prompt for missing values if not. check_properties if [[ "$?" == "1" ]]; then # Check if the '--no-interactive' flag was passed and exit with error. [[ $no_interactive_flag == "1" ]] && log --error "Required properties not set and '--no-interactive' flag was passed." && exit 1 # Only log this if not in interactive mode. [[ $interactive_mode == "0" ]] && log "All required properties not set, prompting for missing properties." prompt_for_properties # Check properties again after prompting in interactive mode. check_properties if [[ "$?" == "1" ]]; then # Exit if they were not set during interactive mode. log --error "You must set app name, app URL, and icon URL!" && exit 1 fi # Set flag that we are in interactive mode. interactive_mode=true fi desktop_file="$XDG_DATA_HOME/applications/$app_name.desktop" # Parse the icon ref and download icon, if applicable. set_icon_ref # Check that an exec command is set, or default to the 'launch-webapp' script. if [[ -z $exec_cmd ]]; then exec_cmd="$SCRIPTS/hypr/launch-webapp $app_url" fi log "\e[032mCreating web app:\e[0m $desktop_file" print_properties if [[ $dry_run == "0" ]]; then generate_file "$desktop_file" fi if [[ $interactive_mode == true ]] && [[ $dry_run == "0" ]]; then log "You can now find $app_name using the app launcher (SUPER + SPACE)\n" fi