feat: Updates install-webapp script with more flags and a launch option to open in a new terminal window.

This commit is contained in:
2025-10-04 12:27:34 -04:00
parent 318e7c7f95
commit 55257e7c28
2 changed files with 239 additions and 133 deletions

View File

@@ -1,24 +1,37 @@
#!/bin/zsh #!/usr/bin/env bash
# Adapted from https://github.com/basecamp/omarchy/tree/master?tab=readme-ov-file # Adapted from https://github.com/basecamp/omarchy/tree/master?tab=readme-ov-file
# TODO: Use bash and only allow options, not positional arguments to make easier. THIS=$(basename ${BASH_SOURCE[0]})
function usage() { function usage() {
cat <<EOF cat <<EOF
Generates a '.desktop' file for a web application, so that it Generates a '.desktop' file for a web application, so that it can act as a stand alone application and
can act as a stand alone application and launched from an application be launched from an application launcher.
launcher.
USAGE: install-webapp [-n <name>] [-u <url>] [-i <icon>] [-e <exec-cmd>] [-m <mime-types>] [-f <file>] [-h] [args...] USAGE: $THIS [OPTIONS]
OPTIONS: OPTIONS:
-n | --name: The name of the application. -n | --name <name>: The name of the application.
-u | --url: The url used to launch the application.
-i | --icon: The icon for the application. -u | --url <url>: The url used to launch the application.
-e | --exec: Custom execution command (optional).
-m | --mime-types: MIME-types for the application (optional). -i | --icon <icon>: The icon for the application.
-f | --file: Install from a spec in a json file.
-e | --exec <cmd>: Custom execution command (optional).
-m | --mime-types <types>: MIME-types for the application (optional).
-f | --file <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. -h | --help: Show usage information.
EXAMPLES: EXAMPLES:
@@ -26,25 +39,18 @@ EXAMPLES:
If no options or arguments are supplied, then it will start an interactive session that prompts for the If no options or arguments are supplied, then it will start an interactive session that prompts for the
values. values.
$ install-webapp $ $THIS
Calling the app with named arguments: Calling the app with named arguments:
$ install-webapp \\ $ $THIS \\
--name "My Awesome App" \\ --name "My Awesome App" \\
--url "https://awesome.com" \\ --url "https://awesome.com" \\
--icon "https://awesome.com/assets/icon.png" --icon "https://awesome.com/assets/icon.png"
Using a json file as input: Using a json file as input:
$ install-webapp --file myapp.json $ $THIS --file myapp.json
It is also possible to use only positional arguments with out their key. They can be passed in the order as
they're listed.
$ install-webapp "My Awesome App" \\
"https://awesome.com" \\
"https://awesome.com/assets/icon.png"
NOTES: NOTES:
@@ -55,7 +61,7 @@ Interactive sessions do not give the option to use a custom execution command or
MIME types, which are less frequently used options. 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 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. An common json spec 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: file example would look like:
{ {
@@ -67,138 +73,238 @@ file example would look like:
EOF EOF
} }
declare -a app_name window_class="com.ghostty.install-webapp"
declare -a app_url
declare -a icon_ref
declare -a custom_exec # Optional custom exec command
declare -a mime_types # Optional mime types
declare -a help_flag
declare -a file_mode
declare -a launch_flag
declare INTERACTIVE_MODE=false
SCRIPTS="${SCRIPTS}"
window_class="com.ghostty.launch-webapp"
window_padding_x="2" 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}"
XDG_DATA_HOME=${XDG_DATA_HOME}
if [[ -z "$SCRIPTS" ]]; then if [[ -z "$SCRIPTS" ]]; then
echo "SCRIPTS not set" echo "SCRIPTS not set"
echo "using ~/.local/scripts" echo "using ~/.local/scripts"
SCRIPTS=$HOME/.local/scripts SCRIPTS=$HOME/.local/scripts
fi fi
zparseopts -D -F -K -- \ if [[ -z "$XDG_DATA_HOME" ]]; then
{n,-name}:=app_name \ echo "XDG_DATA_HOME not set"
{u,-url}:=app_url \ echo "using ~/.local/share"
{i,-icon}:=icon_ref \ XDG_DATA_HOME=$HOME/.local/share
{e,-exec}:=custom_exec \ fi
{m,-mime-types}:=mime_types \
{f,-file}:=file_mode \
{h,-help}=help_flag \
{l,-launch}=launch_flag
[ ${#help_flag[@]} -gt 0 ] && usage && exit 0 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
if [[ ${#launch_flag[@]} -gt 0 ]]; then log() {
if [[ $dry_run == "1" ]]; then
echo -e "\e[34m[DRY_RUN]=>\e[0m $1"
else
echo -e "$1"
fi
}
log_error() {
log "\e[31m[ERROR]:\e[0m $1"
}
launch() {
ghostty --class=$window_class --window-padding-x=$window_padding_x \ ghostty --class=$window_class --window-padding-x=$window_padding_x \
--keybind="ctrl+c=quit" \ --keybind="ctrl+c=quit" \
-e $SCRIPTS/hypr/install-webapp -e "${BASH_SOURCE[0]}" --interactive
exit 0 }
fi
# If passed in as positional arguments, without flags. check_properties() {
[ -n "$1" ] && app_name+=("$1") if [[ -z $app_name ]] || [[ -z $app_url ]] || [[ -z $icon_ref ]]; then
[ -n "$2" ] && app_url+=("$2") return 1
[ -n "$3" ] && icon_ref+=("$3")
[ -n "$4" ] && custom_exec+=("$4")
[ -n "$5" ] && mime_types+=("$5")
# If passed in a json spec file.
if [[ -n "$file_mode" ]]; then
file=$(cat ${file_mode[-1]})
app_name+=$(echo $file | jq -r '.name')
app_url+=$(echo $file | jq -r '.url')
icon_ref+=$(echo $file | jq -r '.icon')
custom_exec+=$(echo $file | jq -r '.exec')
mime_types+=$(echo $file | jq -r '.mime_types')
fi
# Check if proper arguments were passed in. Start interactive mode if not.
if [[ -z "$app_name[-1]" || -z "$app_url[-1]" || -z "$icon_ref[-1]" ]]; then
echo -e "\e[32mLet's create a new web app you can start with the app launcher.\n\e[0m"
app_name+=($(gum input --prompt "Name> " --placeholder "My favorite web app"))
app_url+=($(gum input --prompt "URL> " --placeholder "https://example.com"))
icon_ref+=($(gum input --prompt "Icon URL> " --placeholder "See https://dashboardicons.com (must use PNG!)"))
INTERACTIVE_MODE=true
else
INTERACTIVE_MODE=false
fi
# Ensure valid execution
if [[ -z "$app_name[-1]" || "$app_name[-1]" == "null" ||
-z "$app_url[-1]" || "$app_url[-1]" == "null" ||
-z "$icon_ref[-1]" || "$icon_ref[-1]" == "null" ]]; then
echo "You must set app name, app URL, and icon URL!"
exit 1
fi
APP_NAME=$app_name[-1]
APP_URL=$app_url[-1]
ICON_REF=$icon_ref[-1]
CUSTOM_EXEC=$custom_exec[-1]
MIME_TYPES=$mime_types[-1]
# Refer to local icon or fetch remotely from URL
ICON_DIR="$HOME/.local/share/applications/icons"
# 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"
if curl -sL -o "$ICON_PATH" "$ICON_REF"; then
ICON_PATH="$ICON_DIR/$APP_NAME.png"
else
echo "Error: Failed to download icon."
exit 1
fi fi
else 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. # Check if the icon path is a file.
if [ -f $ICON_REF ]; then if [ -f $icon_ref ]; then
ICON_PATH=$ICON_REF icon_path=$icon_ref
else else
ICON_PATH="$ICON_DIR/$ICON_REF" icon_path="$icon_dir/$icon_ref"
fi
fi fi
fi
# Use custom exec if provided, otherwise default behavior icon_ref=$icon_path
if [[ -n $CUSTOM_EXEC ]] && [[ ! $CUSTOM_EXEC == "null" ]]; then }
EXEC_COMMAND="$CUSTOM_EXEC"
else
EXEC_COMMAND="${SCRIPTS:-$HOME/.local/scripts}/hypr/launch-webapp $APP_URL"
fi
# Create application .desktop file generate_file() {
DESKTOP_FILE="$HOME/.local/share/applications/$APP_NAME.desktop"
cat >"$DESKTOP_FILE" <<EOF cat >"$1" <<EOF
[Desktop Entry] [Desktop Entry]
Version=1.0 Version=1.0
Name=$APP_NAME Name=$app_name
Comment=$APP_NAME Comment=$app_name
Exec=$EXEC_COMMAND Exec=$exec_cmd
Terminal=false Terminal=false
Type=Application Type=Application
Icon=$ICON_PATH Icon=$icon_ref
StartupNotify=true StartupNotify=true
EOF EOF
# Add mime types if provided # Add mime types if provided
if [[ -n $MIME_TYPES ]] && [[ ! $MIME_TYPES == "null" ]]; then if [[ -n $mime_types ]]; then
echo "MimeType=$MIME_TYPES" >>"$DESKTOP_FILE" echo "MimeType=$mime_types" >>"$1"
fi
chmod +x "$1"
}
################################################################################
# MAIN
################################################################################
if [[ $launch_flag == "1" ]]; then
launch && exit 0
fi fi
chmod +x "$DESKTOP_FILE" if [[ $file_mode_flag == "1" ]]; then
load_from_file $json_file
if [[ $INTERACTIVE_MODE == true ]]; then fi
echo -e "You can now find $APP_NAME using the app launcher (SUPER + SPACE)\n"
# 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 fi

2
webapp
View File

@@ -63,7 +63,7 @@ install() {
log "Installing webapp from spec: $file" log "Installing webapp from spec: $file"
if [[ $dry_run == "0" ]]; then if [[ $dry_run == "0" ]]; then
$script --file $file $script --file $file --no-interactive
fi fi
} }