feat: Unifies webapp scripts into a single script with subcommands.

This commit is contained in:
2025-11-10 21:33:02 -05:00
parent e63e4f4fab
commit e57262b4d3
5 changed files with 70 additions and 11 deletions

304
env/.local/scripts/hypr/utils/webapp/install vendored Executable file
View File

@@ -0,0 +1,304 @@
#!/usr/bin/env bash
# Adapted from https://github.com/basecamp/omarchy/tree/master?tab=readme-ov-file
THIS_FILE=${BASH_SOURCE[0]}
THIS=${THIS:-$(basename "$THIS_FILE")}
LOG_FILE=${LOG_FILE:-"$THIS.log"}
LOG_LABEL=$(basename "$THIS_FILE")
function usage() {
cat <<EOF
Generates a '.desktop' file for a web application, so that it can act as a stand alone application and
be launched from an application launcher.
USAGE: $THIS [OPTIONS]
OPTIONS:
-n | --name <name>: The name of the application.
-u | --url <url>: The url used to launch the application.
-i | --icon <icon>: The icon for the application.
-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.
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" <<EOF
[Desktop Entry]
Version=1.0
Name=$app_name
Comment=$app_name
Exec=$exec_cmd
Terminal=false
Type=Application
Icon=$icon_ref
StartupNotify=true
EOF
# Add mime types if provided
if [[ -n $mime_types ]]; then
echo "MimeType=$mime_types" >>"$1"
fi
chmod +x "$1"
}
################################################################################
# MAIN
################################################################################
# Setup logging file and label
source "$SCRIPTS/hypr/logging"
setup-logging "$LOG_FILE" "$LOG_LABEL"
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/webapp launch $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

102
env/.local/scripts/hypr/utils/webapp/launch vendored Executable file
View File

@@ -0,0 +1,102 @@
#! /usr/bin/env bash
# Adapted from https://github.com/basecamp/omarchy/tree/master?tab=readme-ov-file
SCRIPTS="${SCRIPTS:-$HOME/.local/scripts}"
THIS_FILE=${BASH_SOURCE[0]}
LOG_LABEL=$(basename "$THIS_FILE")
THIS=${THIS:-$(basename "$THIS_FILE")}
LOG_FILE=${LOG_FILE:-"$LOG_LABEL.log"}
usage() {
cat <<EOF
Launches a url as a web application. This script relys on the 'launch' script. This
essentially just generates the pattern and launch command to pass into that script.
USAGE:
$ $THIS [OPTIONS] <url> [ARGS...]
OPTIONS:
-f | --or-focus: If a window exists matching the url's domain, focus it
instead of launching new window.
-s | --special <name>: Launch in the special workspace name, or toggle the special
workspace.
-h | --help: Show this help page.
NOTES:
Any extra arguments after '--' get passed directly to the browser invocation.
$ $THIS https://example.com -- --some-random-flag-for-browser=1
Any options passed in prior to the '--' get sent to the 'launch' script, so you can pass
options that are not specifically shown here, but the ones shown would be the most commonly
used, so they are documented here.
Using the '--special' flag is useful for apps that you want to have a "summoning" like behavior.
Upon first launch the application will be opened and the special workspace will be shown.
Calling it again will keep the application open in the special workspace but hide the workspace.
Further calls will not open another instance of the application, but will toggle the visiblity
of the special workspace.
EOF
}
browser="brave-browser.desktop"
url=""
launch_args=()
app_args=""
while [[ $# -gt 0 ]]; do
if [[ $1 =~ ^--special ]] || [[ $1 =~ ^-s ]]; then
launch_args+=("$1")
launch_args+=("$2")
shift # Second shift get's handled below
elif [[ $1 =~ ^--help ]] || [[ $1 =~ ^-h ]]; then
usage && exit 0
elif [[ -z $url ]] && [[ ! $1 =~ ^- ]]; then
url=$1
elif [[ $1 == "--" ]]; then
shift
break
else
launch_args+=("$1")
fi
shift
done
# Strips url down to just the domain, so that we can match window classes.
pattern() {
pattern=${url/#https:\/\//}
pattern=${pattern/#http:\/\//}
pattern=${pattern%%/*}
echo $pattern
}
log() {
logging log --source "$THIS_FILE" "$@"
}
##################################################
# MAIN
##################################################
# setup logging file and label
source "$SCRIPTS/hypr/logging"
setup-logging "$LOG_FILE" "$LOG_LABEL"
if [[ -z $url ]]; then
log --error "Must supply a url." && usage && exit 1
fi
# Any left over args after "--"
app_args="$@"
log "Launching URL: $url"
log " Launch args: ${launch_args[@]}"
log " App args: ${app_args}"
$SCRIPTS/hypr/launch "${launch_args[@]}" "$(pattern)" \
setsid uwsm app -- $(sed -n 's/^Exec=\([^ ]*\).*/\1/p' {~/.local,~/.nix-profile,/usr}/share/applications/$browser 2>/dev/null | head -1) --app="$url" "$app_args"