mirror of
https://github.com/The-Repo-Club/DotFiles.git
synced 2024-11-25 00:38:20 -05:00
243 lines
9.1 KiB
Plaintext
243 lines
9.1 KiB
Plaintext
|
#!/usr/bin/env bash
|
||
|
# Author: Lu Xu <oliver_lew at outlook dot com>
|
||
|
# License: MIT
|
||
|
# References: https://specifications.freedesktop.org/desktop-entry-spec
|
||
|
# https://specifications.freedesktop.org/icon-theme-spec
|
||
|
|
||
|
# XDG directories
|
||
|
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
|
||
|
XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}
|
||
|
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
|
||
|
XDG_DATA_DIRS=${XDG_DATA_DIRS:-/usr/share:/usr/local/share}
|
||
|
|
||
|
# Apply default configuration values
|
||
|
SHOW_ALL=${XDG_MENU_SHOW_ALL:-}
|
||
|
TERMINAL=${XDG_MENU_TERMINAL:-xterm}
|
||
|
SIZE=${XDG_MENU_ICON_SIZE:-24}
|
||
|
ICON_THEME=${XDG_MENU_ICON_THEME:-$(
|
||
|
if command -v gtk-query-settings > /dev/null; then
|
||
|
gtk-query-settings gtk-icon-theme-name | awk '{ print $2 }' | tr -d '"'
|
||
|
else
|
||
|
echo Adwaita
|
||
|
fi
|
||
|
)}
|
||
|
FALLBACK_ICON=${XDG_MENU_FALLBACK_ICON:-application-x-executable}
|
||
|
|
||
|
# Read CLI arguments
|
||
|
while getopts "ab:fhi:ns:t:" arg; do
|
||
|
case "$arg" in
|
||
|
a) SHOW_ALL=1;;
|
||
|
b) FALLBACK_ICON=$OPTARG;;
|
||
|
f) force_cache=1;;
|
||
|
i) ICON_THEME=$OPTARG;;
|
||
|
n) not_cache=1;;
|
||
|
s) SIZE=$OPTARG;;
|
||
|
t) TERMINAL=$OPTARG;;
|
||
|
h|*) echo "$0 [-a] [-b icon] [-f] [-i theme] [-n] [-s size] [-t term]" && exit ;;
|
||
|
esac
|
||
|
done
|
||
|
|
||
|
# Directories
|
||
|
DATA_DIRS=$XDG_DATA_HOME:$XDG_DATA_DIRS
|
||
|
CACHE_DIR="$XDG_CACHE_HOME/xdg-xmenu"
|
||
|
CACHE_ICON_DIR="$CACHE_DIR/icons"
|
||
|
mkdir -p "$CACHE_ICON_DIR"
|
||
|
|
||
|
# Find directories in an icon theme matching the target size
|
||
|
valid_dirs() {
|
||
|
theme_dir=$1
|
||
|
[ -f "$theme_dir/index.theme" ] || return
|
||
|
while IFS='=' read -r key value; do
|
||
|
# parse the index.theme file
|
||
|
case "$key" in
|
||
|
\[*\])
|
||
|
# FIXME: scaled icons are ignored for now, sorry HiDPI
|
||
|
# deal with last subdirectory, only Size is mandatory
|
||
|
if [ "${Scale-1}" = 1 ] && [ -n "$Size" ]; then
|
||
|
# defaults if they are not specified
|
||
|
Type=${Type:-Threshold}
|
||
|
MaxSize=${MaxSize:-Size}
|
||
|
MinSize=${MinSize:-Size}
|
||
|
Threshold=${Threshold:-2}
|
||
|
|
||
|
# match subdirectory sizes based on 'Type'
|
||
|
# 1. fixed size, has to be exact
|
||
|
{ [ "$Type" = Fixed ] &&
|
||
|
[ "$SIZE" = "$Size" ]; } ||
|
||
|
# 2. threshold size, within Threshold times
|
||
|
{ [ "$Type" = Threshold ] &&
|
||
|
[ "$SIZE" -ge "$((Size / Threshold))" ] &&
|
||
|
[ "$SIZE" -le "$((Size * Threshold))" ]; } ||
|
||
|
# 3. scalable size, Min/MaxSize as lower/upper limits
|
||
|
{ [ "$Type" = Scalable ] &&
|
||
|
[ "$SIZE" -ge "$MinSize" ] &&
|
||
|
[ "$SIZE" -le "$MaxSize" ]; } &&
|
||
|
# found it
|
||
|
echo "$theme_dir/$subdir"
|
||
|
fi
|
||
|
|
||
|
# clean up variables for the new subdirectory
|
||
|
unset MaxSize MinSize Scale Size Threshold Type subdir
|
||
|
subdir=${key#[}
|
||
|
subdir=${subdir%]}
|
||
|
;;
|
||
|
*)
|
||
|
# read each key-value pair into shell variables
|
||
|
[ -n "$value" ] && eval "$key=\"$value\""
|
||
|
;;
|
||
|
esac
|
||
|
done < "$theme_dir/index.theme"
|
||
|
}
|
||
|
|
||
|
# Find all directories matching the size and containing icons directly
|
||
|
find_all_dirs() {
|
||
|
IFS=':'
|
||
|
for datadir in $DATA_DIRS; do unset IFS
|
||
|
for theme in "$ICON_THEME" "hicolor"; do
|
||
|
valid_dirs "$datadir/icons/$theme"
|
||
|
done
|
||
|
done
|
||
|
echo "/usr/share/pixmaps"
|
||
|
}
|
||
|
|
||
|
all_dirs=$(find_all_dirs)
|
||
|
|
||
|
# Search for an icon based on its name
|
||
|
find_icon() {
|
||
|
icon=$1
|
||
|
# find any png, svg or xpm icon in available directories
|
||
|
for dir in $all_dirs; do
|
||
|
for ext in png svg xpm; do
|
||
|
if [ -f "$dir/$icon.$ext" ]; then
|
||
|
echo "$dir/$icon.$ext"
|
||
|
return
|
||
|
fi
|
||
|
done
|
||
|
done
|
||
|
# when the fallback icon is not found, return none instead
|
||
|
[ "$icon" = "$FALLBACK_ICON" ] && echo "" || echo "$FALLBACK_ICON"
|
||
|
}
|
||
|
|
||
|
# xmenu does not support svg, so convert svg to png
|
||
|
cache_icon() {
|
||
|
icon=$1
|
||
|
|
||
|
# icon is a existing png/xpm file
|
||
|
[ -f "$icon" ] && [ ${icon##*.} != 'svg' ] && echo "$icon" && return
|
||
|
|
||
|
# cache path in $CACHE_ICON_DIR with the flattened name
|
||
|
[ -f "$icon" ] && basename=$(echo $icon | tr '/' '_') || basename=${icon##*/}
|
||
|
cache_path=$CACHE_ICON_DIR/$basename.png
|
||
|
|
||
|
# already cached, skip unless forced to cache again
|
||
|
if [ -f "$cache_path" ] && [ -z "$force_cache" ]; then
|
||
|
echo "$cache_path" && return
|
||
|
fi
|
||
|
|
||
|
# find the icon in icon themes if the name is not a path
|
||
|
[ -f "$icon" ] && icon_path=$icon || icon_path=$(find_icon "$icon")
|
||
|
|
||
|
# convert svg -> png
|
||
|
if [ "${icon_path##*.}" = "svg" ]; then
|
||
|
# first try the fasted, then the slower ones
|
||
|
if [ -n "$not_cache" ]; then
|
||
|
: # not doing caching in dry run mode
|
||
|
elif command -v rsvg-convert > /dev/null; then
|
||
|
rsvg-convert -w "$SIZE" -h "$SIZE" "$icon_path" -o "$cache_path"
|
||
|
elif command -v convert > /dev/null; then
|
||
|
convert -background none -size "${SIZE}x${SIZE}" "$icon_path" "$cache_path"
|
||
|
elif command -v inkscape > /dev/null; then
|
||
|
inkscape -w "$SIZE" -h "$SIZE" "$icon_path" -o "$cache_path"
|
||
|
else
|
||
|
echo >&2 "[Error]: Convert svg icons failed."
|
||
|
echo >&2 "Please install one of the following programs:"
|
||
|
echo >&2 "rsvg-convert(librsvg), convert(imagemagick), inkscape"
|
||
|
exit 1
|
||
|
fi
|
||
|
echo "$cache_path"
|
||
|
else
|
||
|
# use png and xpm icons in-place
|
||
|
# this could be empty if no fallback icon is found, and it will work
|
||
|
echo "$icon_path"
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
FALLBACK_ICON=$(cache_icon "$FALLBACK_ICON")
|
||
|
|
||
|
# parse the desktop entries and categorize them
|
||
|
IFS=:
|
||
|
for datadir in $DATA_DIRS; do unset IFS
|
||
|
appdir="$datadir/applications"
|
||
|
[ ! -d "$appdir" ] && continue
|
||
|
for app in "$appdir"/*.desktop; do
|
||
|
unset icon name genname cmd category nodisplay useterm added
|
||
|
while IFS="=" read -r key value; do
|
||
|
case "$key" in
|
||
|
Categories) [ -z "$category" ] && category=$value ;;
|
||
|
Icon) [ -z "$icon" ] && icon=$(cache_icon "$value") ;;
|
||
|
Name) [ -z "$name" ] && name="$value" ;;
|
||
|
GenericName)[ -z "$genname" ] && genname=$value ;;
|
||
|
Exec) [ -z "$cmd" ] && cmd=$value &&
|
||
|
# remove %* place holders
|
||
|
[ "${cmd#*%}" != "$cmd" ] && cmd=$(
|
||
|
echo "$cmd" | sed 's/ %[fFuUick]//g' | sed 's/%%/$/g'
|
||
|
) ;;
|
||
|
NoDisplay) [ "$value" != "false" ] && nodisplay=1 ;;
|
||
|
Terminal) [ "$value" != "false" ] && useterm=1 ;;
|
||
|
# FIXME: show_all not working as it should
|
||
|
OnlyShowIn) [ -z "$SHOW_ALL" ] && nodisplay=1 ;;
|
||
|
esac
|
||
|
done < "$app"
|
||
|
|
||
|
[ -n "$nodisplay" ] && continue
|
||
|
[ -n "$useterm" ] && cmd="$TERMINAL -e $cmd"
|
||
|
[ -n "$genname" ] && name="$name ($genname)"
|
||
|
icon="${icon-$FALLBACK_ICON}"
|
||
|
icon="${icon:+\tIMG:$icon}"
|
||
|
item="$icon\t$name\t$cmd"
|
||
|
|
||
|
IFS=\;
|
||
|
for c in $category; do unset IFS
|
||
|
case "$c" in
|
||
|
Audio*|*Video) added=1; Multimedia="$Multimedia$item\n" ;;
|
||
|
Development) added=1; Development="$Development$item\n" ;;
|
||
|
Education) added=1; Education="$Education$item\n" ;;
|
||
|
Game) added=1; Games="$Games$item\n" ;;
|
||
|
Graphics) added=1; Graphics="$Graphics$item\n" ;;
|
||
|
Network) added=1; Internet="$Internet$item\n" ;;
|
||
|
Office) added=1; Office="$Office$item\n" ;;
|
||
|
Science) added=1; Science="$Science$item\n" ;;
|
||
|
Settings) added=1; Settings="$Settings$item\n" ;;
|
||
|
System) added=1; System="$System$item\n" ;;
|
||
|
Utility) added=1; Accessories="$Accessories$item\n" ;;
|
||
|
esac
|
||
|
done
|
||
|
[ -z "$added" ] && Others="$Others$item\n"
|
||
|
done
|
||
|
done
|
||
|
|
||
|
category_sort() {
|
||
|
category_icon=$1
|
||
|
category_name=$2
|
||
|
category_apps=$3
|
||
|
icon=$(cache_icon "$category_icon")
|
||
|
# create top level folder
|
||
|
printf "${icon:+IMG:$icon\t}%s\n" "$category_name"
|
||
|
# sort the applications by their names
|
||
|
# FIXME: when icon is missing, name is in first column
|
||
|
printf "$category_apps%s" | sort -f -k 2 | uniq
|
||
|
}
|
||
|
|
||
|
category_sort applications-accessories Accessories "$Accessories"
|
||
|
category_sort applications-development Development "$Development"
|
||
|
category_sort applications-education Education "$Education"
|
||
|
category_sort applications-games Games "$Games"
|
||
|
category_sort applications-graphics Graphics "$Graphics"
|
||
|
category_sort applications-internet Internet "$Internet"
|
||
|
category_sort applications-multimedia Multimedia "$Multimedia"
|
||
|
category_sort applications-office Office "$Office"
|
||
|
category_sort applications-science Science "$Science"
|
||
|
category_sort preferences-desktop Settings "$Settings"
|
||
|
category_sort applications-system System "$System"
|
||
|
category_sort applications-other Others "$Others"
|