gentoo/eclass/desktop.eclass
Andreas Sturmlechner 9ab45cb772
desktop.eclass: Hello 2025
Signed-off-by: Andreas Sturmlechner <asturm@gentoo.org>
2025-12-08 13:28:09 +01:00

531 lines
13 KiB
Bash

# Copyright 1999-2025 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
# @ECLASS: desktop.eclass
# @MAINTAINER:
# base-system@gentoo.org
# @SUPPORTED_EAPIS: 7 8
# @BLURB: support for desktop files, menus, and icons
if [[ -z ${_DESKTOP_ECLASS} ]]; then
_DESKTOP_ECLASS=1
case ${EAPI} in
7|8) ;;
*) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;;
esac
# @ECLASS_VARIABLE: _DESKTOP_IDS
# @DEFAULT_UNSET
# @DESCRIPTION:
# Internal array containing any app-ids used by make_desktop_entry() calls.
# Lets us keep track of/avoid duplicate desktop file names.
_DESKTOP_IDS=()
# @FUNCTION: make_desktop_entry
# @USAGE: [--eapi9] <command> [options]
# @DESCRIPTION:
# Make a .desktop file and install it in /usr/share/applications/.
#
# @CODE
# --eapi9: Switch to getopts style arguments instead of order based
# As the naming implies, this is off by default for EAPI=[78],
# but mandated by future EAPI.
# command: Exec command the app is being run with, also base for TryExec
# --- Options:
# name: Name that will show up in the menu; defaults to PN
# with --eapi9: must not contain arguments, use --args for that
# icon: Icon to use with the menu entry; defaults to PN
# this can be relative (to /usr/share/pixmaps) or
# a full path to an icon
# categories: Categories for this kind of application. Examples:
# https://specifications.freedesktop.org/menu-spec/latest/apa.html
# if unset, function tries to guess from package's category
# entry: Key=Value entry to append to the desktop file;
# with --eapi9: multiple allowed; old style: a printf string
# --- Additional parameters available using --eapi9:
# args: Arguments (binary params and desktop spec field codes) to add
# to Exec value, separated by a space if multiple
# desktopid: <desktopid>.desktop will be created. Must be same as "app id"
# defined in code (including reverse qualified domain if set);
# defaults to <command>
# comment: Comment (menu entry tooltip), defaults to DESCRIPTION
# @CODE
#
# Example usage:
# @CODE
# Deprecated, in order:
# <command> [name] [icon] [categories] [entries...]
# New style:
# --eapi9 <command> [-a args] [-d desktopid] [-C comment] [-i icon]
# --eapi9 <command> [-n name] [-e entry...] [-c categories]
# @CODE
make_desktop_entry() {
local eapi9
if [[ -n ${1} ]]; then
case ${EAPI} in
7|8)
if [[ ${1} == --eapi9 ]]; then
eapi9=1
shift
fi
;;
*)
if [[ ${1} == --eapi9 ]]; then
ewarn "make_desktop_entry: --eapi9 arg is obsolete in EAPI-${EAPI} and may be cleaned up now."
shift
fi
eapi9=1
;;
esac
fi
[[ -z ${1} ]] && die "make_desktop_entry: You must specify at least a command"
if [[ ${eapi9} ]]; then
local args cats cmd comment desktopid entries icon name
while [[ $# -gt 0 ]] ; do
case "${1}" in
-a|--args)
args="${2}"; shift 2 ;;
-c|--categories)
cats="${2}"; shift 2 ;;
-C|--comment)
comment="${2}"; shift 2 ;;
-d|--desktopid)
desktopid="${2}"; shift 2 ;;
-e|--entry)
entries+=( "${2}" ); shift 2 ;;
-i|--icon)
icon="${2}"; shift 2 ;;
-n|--name)
name="${2}"; shift 2 ;;
*)
if [[ -z ${cmd} ]] ; then
cmd="${1}"
else
die "make_desktop_entry: Can only take one command! First got: ${cmd}; then got: ${1}"
fi
shift ;;
esac
done
[[ -z ${cmd} ]] && die "make_desktop_entry: You must specify at least a command"
[[ -z ${name} ]] && name=${PN}
[[ -z ${icon} ]] && icon=${PN}
else
local cmd=${1}
local name=${2:-${PN}}
local icon=${3:-${PN}}
local cats=${4}
local entries=${5}
fi
[[ -z ${comment} ]] && comment="${DESCRIPTION}"
if [[ -z ${cats} ]] ; then
local catmaj=${CATEGORY%%-*}
local catmin=${CATEGORY##*-}
case ${catmaj} in
app)
case ${catmin} in
accessibility) cats="Utility;Accessibility";;
admin) cats=System;;
antivirus) cats=System;;
arch) cats="Utility;Archiving";;
backup) cats="Utility;Archiving";;
cdr) cats="AudioVideo;DiscBurning";;
dicts) cats="Office;Dictionary";;
doc) cats=Documentation;;
editors) cats="Utility;TextEditor";;
emacs) cats="Development;TextEditor";;
emulation) cats="System;Emulator";;
laptop) cats="Settings;HardwareSettings";;
office) cats=Office;;
pda) cats="Office;PDA";;
vim) cats="Development;TextEditor";;
xemacs) cats="Development;TextEditor";;
esac
;;
dev)
cats="Development"
;;
games)
case ${catmin} in
action|fps) cats=ActionGame;;
arcade) cats=ArcadeGame;;
board) cats=BoardGame;;
emulation) cats=Emulator;;
kids) cats=KidsGame;;
puzzle) cats=LogicGame;;
roguelike) cats=RolePlaying;;
rpg) cats=RolePlaying;;
simulation) cats=Simulation;;
sports) cats=SportsGame;;
strategy) cats=StrategyGame;;
esac
cats="Game;${cats}"
;;
gnome)
cats="Gnome;GTK"
;;
kde)
cats="KDE;Qt"
;;
mail)
cats="Network;Email"
;;
media)
case ${catmin} in
gfx)
cats=Graphics
;;
*)
case ${catmin} in
radio) cats=Tuner;;
sound) cats=Audio;;
tv) cats=TV;;
video) cats=Video;;
esac
cats="AudioVideo;${cats}"
;;
esac
;;
net)
case ${catmin} in
dialup) cats=Dialup;;
ftp) cats=FileTransfer;;
im) cats=InstantMessaging;;
irc) cats=IRCClient;;
mail) cats=Email;;
news) cats=News;;
nntp) cats=News;;
p2p) cats=FileTransfer;;
voip) cats=Telephony;;
esac
cats="Network;${cats}"
;;
sci)
case ${catmin} in
astro*) cats=Astronomy;;
bio*) cats=Biology;;
calc*) cats=Calculator;;
chem*) cats=Chemistry;;
elec*) cats=Electronics;;
geo*) cats=Geology;;
math*) cats=Math;;
physics) cats=Physics;;
visual*) cats=DataVisualization;;
esac
cats="Education;Science;${cats}"
;;
sys)
cats="System"
;;
www)
case ${catmin} in
client) cats=WebBrowser;;
esac
cats="Network;${cats}"
;;
*)
cats=
;;
esac
fi
if [[ ${eapi9} ]]; then
if [[ -z ${desktopid} ]]; then
if [[ ${cmd} =~ .+[[:space:]].+ ]]; then
die "make_desktop_entry: --desktopid must be provided when <command> contains a space"
fi
desktopid="${cmd##*/}"
fi
if [[ ! ${desktopid} =~ ^[A-Za-z0-9._-]+$ ]]; then
die "make_desktop_entry: <desktopid> must only consist of ASCII letters, digits, dash, underscore and dots"
fi
if [[ ${_DESKTOP_IDS[*]} =~ (^|[[:space:]])"${desktopid}"($|[[:space:]]) ]]; then
die "make_desktop_entry: desktopid \"${desktopid}\" already used in a previous call, choose a different one"
else
_DESKTOP_IDS+=( "${desktopid}" )
fi
local desktop="${T}/${desktopid}.desktop"
else
local desktop_exec="${cmd%%[[:space:]]*}"
desktop_exec="${desktop_exec##*/}"
local desktop_suffix="-${PN}"
[[ ${SLOT%/*} != 0 ]] && desktop_suffix+="-${SLOT%/*}"
# Replace foo-foo.desktop by foo.desktop
[[ ${desktop_suffix#-} == "${desktop_exec}" ]] && desktop_suffix=""
# Prevent collisions if a file with the same name already exists #771708
local desktop="${desktop_exec}${desktop_suffix}" count=0
while [[ -e ${ED}/usr/share/applications/${desktop}.desktop ]]; do
desktop="${desktop_exec}-$((++count))${desktop_suffix}"
done
desktop="${T}/${desktop}.desktop"
fi
# Don't append another ";" when a valid category value is provided.
cats=${cats%;}${cats:+;}
if [[ -n ${icon} && ${icon} != /* ]] && [[ ${icon} == *.xpm || ${icon} == *.png || ${icon} == *.svg ]]; then
ewarn "As described in the Icon Theme Specification, icon file extensions are not"
ewarn "allowed in .desktop files if the value is not an absolute path."
icon=${icon%.*}
fi
cat > "${desktop}" <<- _EOF_ || die
[Desktop Entry]
Type=Application
Name=${name}
Comment=${comment}
Icon=${icon}
Categories=${cats}
_EOF_
if [[ ${eapi9} ]]; then
local cmd_args="${cmd} ${args}"
cat >> "${desktop}" <<- _EOF_ || die
Exec=${cmd_args%[[:space:]]}
TryExec=${cmd}
_EOF_
else
cat >> "${desktop}" <<- _EOF_ || die
Exec=${cmd}
TryExec=${cmd%% *}
_EOF_
fi
if [[ ${eapi9} && -n ${entries} ]]; then
local entry
for entry in ${entries[@]}; do
if [[ ${entry} =~ ^[A-Za-z0-9-]+=.* ]]; then
printf "%s\n" "${entry}" >> "${desktop}" || die
else
die "make_desktop_entry: <entry> \"${entry}\" rejected; must be passed a Key=Value pair"
fi
done
else
if [[ ${entries:-=} != *=* ]]; then
# 5th arg used to be value to Path=
ewarn "make_desktop_entry: update your 5th arg to read Path=${entries}"
entries="Path=${entries}"
fi
if [[ -n ${entries} ]]; then
printf '%b\n' "${entries}" >> "${desktop}" || die
fi
fi
(
# wrap the env here so that the 'insinto' call
# doesn't corrupt the env of the caller
insopts -m 0644
insinto /usr/share/applications
doins "${desktop}"
)
}
# @FUNCTION: make_session_desktop
# @USAGE: <title> <command> [command args...]
# @DESCRIPTION:
# Make a GDM/KDM Session file. The title is the file to execute to start the
# Window Manager. The command is the name of the Window Manager.
#
# You can set the name of the file via the ${wm} variable.
make_session_desktop() {
[[ -z $1 ]] && eerror "$0: You must specify the title" && return 1
[[ -z $2 ]] && eerror "$0: You must specify the command" && return 1
local title=$1
local command=$2
local desktop=${T}/${wm:-${PN}}.desktop
shift 2
cat <<-EOF > "${desktop}" || die
[Desktop Entry]
Name=${title}
Comment=This session logs you into ${title}
Exec=${command} $*
TryExec=${command}
Type=XSession
EOF
(
# wrap the env here so that the 'insinto' call
# doesn't corrupt the env of the caller
insopts -m 0644
insinto /usr/share/xsessions
doins "${desktop}"
)
}
# @FUNCTION: domenu
# @USAGE: <menus>
# @DESCRIPTION:
# Install the list of .desktop menu files into the appropriate directory
# (/usr/share/applications).
domenu() {
(
# wrap the env here so that the 'insinto' call
# doesn't corrupt the env of the caller
local i ret=0
insopts -m 0644
insinto /usr/share/applications
for i in "$@" ; do
if [[ -d ${i} ]] ; then
doins "${i}"/*.desktop
((ret|=$?))
else
doins "${i}"
((ret|=$?))
fi
done
exit ${ret}
)
}
# @FUNCTION: newmenu
# @USAGE: <menu> <newname>
# @DESCRIPTION:
# Like all other new* functions, install the specified menu as newname.
newmenu() {
(
# wrap the env here so that the 'insinto' call
# doesn't corrupt the env of the caller
insopts -m 0644
insinto /usr/share/applications
newins "$@"
)
}
# @FUNCTION: _iconins
# @INTERNAL
# @DESCRIPTION:
# function for use in doicon and newicon
_iconins() {
(
# wrap the env here so that the 'insinto' call
# doesn't corrupt the env of the caller
insopts -m 0644
local funcname=$1; shift
local size dir
local context=apps
local theme=hicolor
while [[ $# -gt 0 ]] ; do
case $1 in
-s|--size)
if [[ ${2%%x*}x${2%%x*} == "$2" ]] ; then
size=${2%%x*}
else
size=${2}
fi
case ${size} in
16|22|24|32|36|48|64|72|96|128|192|256|512|1024)
size=${size}x${size};;
symbolic|scalable)
;;
*)
eerror "${size} is an unsupported icon size!"
exit 1;;
esac
shift 2;;
-t|--theme)
theme=${2}
shift 2;;
-c|--context)
context=${2}
shift 2;;
*)
if [[ -z ${size} ]] ; then
insinto /usr/share/pixmaps
else
insinto /usr/share/icons/${theme}/${size}/${context}
fi
if [[ ${funcname} == doicon ]] ; then
if [[ -f $1 ]] ; then
doins "${1}"
elif [[ -d $1 ]] ; then
shopt -s nullglob
doins "${1}"/*.{png,svg}
shopt -u nullglob
else
eerror "${1} is not a valid file/directory!"
exit 1
fi
else
break
fi
shift 1;;
esac
done
if [[ ${funcname} == newicon ]] ; then
newins "$@"
fi
)
}
# @FUNCTION: doicon
# @USAGE: [options] <icons>
# @DESCRIPTION:
# Install icon into the icon directory /usr/share/icons or into
# /usr/share/pixmaps if "--size" is not set.
# This is useful in conjunction with creating desktop/menu files.
#
# @CODE
# options:
# -s, --size
# !!! must specify to install into /usr/share/icons/... !!!
# size of the icon, like 48 or 48x48
# supported icon sizes are:
# 16 22 24 32 36 48 64 72 96 128 192 256 512 1024 scalable
# -c, --context
# defaults to "apps"
# -t, --theme
# defaults to "hicolor"
#
# icons: list of icons
#
# example 1: doicon foobar.png fuqbar.svg suckbar.png
# results in: insinto /usr/share/pixmaps
# doins foobar.png fuqbar.svg suckbar.png
#
# example 2: doicon -s 48 foobar.png fuqbar.png blobbar.png
# results in: insinto /usr/share/icons/hicolor/48x48/apps
# doins foobar.png fuqbar.png blobbar.png
# @CODE
doicon() {
_iconins ${FUNCNAME} "$@"
}
# @FUNCTION: newicon
# @USAGE: [options] <icon> <newname>
# @DESCRIPTION:
# Like doicon, install the specified icon as newname.
#
# @CODE
# example 1: newicon foobar.png NEWNAME.png
# results in: insinto /usr/share/pixmaps
# newins foobar.png NEWNAME.png
#
# example 2: newicon -s 48 foobar.png NEWNAME.png
# results in: insinto /usr/share/icons/hicolor/48x48/apps
# newins foobar.png NEWNAME.png
# @CODE
newicon() {
_iconins ${FUNCNAME} "$@"
}
fi