about summary refs log tree commit diff stats
path: root/autoshart
blob: a1d265b7bd2479e5a60930fb06f0ee778fe79942 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#!/bin/sh
# AUTOSHART: autostart *.desktop files
# Copyright (C) 2022 Case Duckworth <acdw@acdw.net>
# Licensed under the Good Choices License.  See COPYING for details.
#
# Commentary:
#
# An autostart script.  Called "autoshart" because I literally sharted it out,
# and have /not/ tested it at all. So, caveat emptor, I guess.
#
# Freedesktop autostart specification:
# https://specifications.freedesktop.org/autostart-spec/latest/
# Freedesktop desktop entry specification:
# https://specifications.freedesktop.org/desktop-entry-spec/latest/
#
# Prior art:
# - dex -- https://github.com/jceb/dex
#
# Code:

usage() {
	cat <<EOF
AUTOSHART: autostart without the shit
USAGE:	autoshart -h
	autoshart [-k] [-n] [-q]

FLAGS:
 -h	Show this help and exit.
 -k	Kill all processes started by autoshart and exit.
 -n	Only print what would happen; don't execute anything.
 -q	Don't print any output.
EOF
	exit "$1"
}

main() {
	PID_FILE="${AUTOSHART_PID_FILE:-/tmp/autoshart.pid}"
	FOUND_FILE="${AUTOSHART_FOUND_FILE:-/tmp/autoshart.found}"
	RUN=true
	QUIET=false
	while getopts hknq opt; do
		case "$opt" in
		h) usage ;;
		k) auto_kill ;;
		n) RUN=false ;;
		q) QUIET=true ;;
		*)
			echo >&2 "Unknown option: -$opt"
			usage 1
			;;
		esac
	done
	shift "$((OPTIND - 1))"
	auto_search
}

## Library functions

auto_kill() {
	xargs kill <"$PID_FILE"
}

auto_start() { ## auto_start FILE
	eprint "$1"
	# When the .desktop file has the Hidden key set to true, the .desktop
	# file MUST be ignored. When multiple .desktop files with the same name
	# exists in multiple directories then only the Hidden key in the most
	# important .desktop file must be considered: If it is set to true all
	# .desktop files with the same name in the other directories MUST be
	# ignored as well.
	hidden="$(get Hidden "$1")"
	test "$hidden" = true && return "$(die 1 "Stopped: Hidden: $1")"
	# This specification defines 3 types of desktop entries: Application
	# (type 1), Link (type 2) and Directory (type 3). To allow the addition
	# of new types in the future, implementations should ignore desktop
	# entries with an unknown type. (REQUIRED)
	type="$(get Type "$1")"
	test "$type" = Application || return "$(die 2 "Error: Bad desktop type: ${type:-[none]}")"
	# Specific name of the application, for example "Mozilla".  (REQUIRED)
	name="$(get Name "$1" | head -n1)"
	test -n "$name" || return "$(die 3 "Error: No application name")"
	# Program to execute, possibly with arguments. See the Exec key for
	# details on how this key works. The Exec key is required if
	# DBusActivatable is not set to true. Even if DBusActivatable is true,
	# Exec should be specified for compatibility with implementations that
	# do not understand DBusActivatable. (Makes no sense to be omitted in an
	# autostart context --- acdw)
	exec="$(get Exec "$1")"
	test -n "$exec" || return "$(die 4 "Error: No Exec key: $1")"
	# The OnlyShowIn entry may contain a list of strings identifying
	# the desktop environments that MUST autostart this application, all
	# other desktop environments MUST NOT autostart this application.  The
	# NotShowIn entry may contain a list of strings identifying the desktop
	# environments that MUST NOT autostart this application, all other
	# desktop environments MUST autostart this application.  Only one of
	# these keys, either OnlyShowIn or NotShowIn, may appear in a single
	# .desktop file.
	#
	### The OnlyShowIn, NotShowIn keys are specified thus:
	#
	# A list of strings identifying the desktop environments that should
	# display/not display a given desktop entry.
	#
	# By default, a desktop file should be shown, unless an OnlyShowIn key
	# is present, in which case, the default is for the file not to be
	# shown.
	#
	# If $XDG_CURRENT_DESKTOP is set then it contains a colon-separated list
	# of strings. In order, each string is considered. If a matching entry
	# is found in OnlyShowIn then the desktop file is shown. If an entry is
	# found in NotShowIn then the desktop file is not shown. If none of the
	# strings match then the default action is taken (as above).
	#
	# $XDG_CURRENT_DESKTOP should have been set by the login manager,
	# according to the value of the DesktopNames found in the session
	# file. The entry in the session file has multiple values separated in
	# the usual way: with a semicolon.
	#
	# The same desktop name may not appear in both OnlyShowIn and NotShowIn
	# of a group.
	#
	### I think this "functionality" is dubious at best and pretty
	### ... silly.  --- acdw
	onlyshowin="$(get OnlyShowIn "$1")"
	notshowin="$(get NotShowIn "$1")"
	if test -n "$onlyshowin" && test -n "$notshowin"; then
		return "$(die 5 "Error: Incompatible keys: OnlyShowIn, NotShowIn: $1")"
	elif test -n "$onlyshowin"; then
		start=false
	else
		start=true
	fi

	# This is so cursed
	quit="$(
		echo "$XDG_CURRENT_DESKTOP" | tr : \\n |
			while read -r desktop; do
				case "$onlyshowin;$notshowin" in
				*$desktop*)
					if test -n "$onlyshowin"; then
						break
					elif test -n "$notshowin"; then
						echo true
						die 6 "Stopped: NotShowIn=$notshowin"
					fi
					;;
				*)
					if ! $start; then
						echo true
						die 6 "Stopped: OnlyShowIn=$onlyshowin"
					fi
					;;
				esac
			done
		echo false
	)"
	if $quit; then
		return 6
	fi

	# A .desktop file with a non-empty TryExec field MUST NOT be autostarted
	# if the value of the TryExec key does NOT match with an installed
	# executable program. The value of the TryExec field may either be an
	# absolute path or the name of an executable without any path
	# components. If the name of an executable is specified without any path
	# components then the $PATH environment is searched to find a matching
	# executable program.
	tryexec="$(get TryExec "$1")"
	if test -n "$tryexec"; then
		command -v "$tryexec" | grep -v ^alias | grep -q / ||
			return "$(die 7 "Error: TryExec command not found: $tryexec")"
	fi
	# If all these tests pass, we're ready to go
	# XXX: probably won't do quoting properly
	eprint "> $exec"
	if $RUN; then
		$exec &
		print $! >>"$PID_FILE"
	fi
	:
}

auto_search() { ## auto_search
	#  The Autostart Directories are $XDG_CONFIG_DIRS/autostart as defined
	#  in accordance with the "Referencing this specification" section in
	#  the "desktop base directory specification".
	#
	# If the same filename is located under multiple Autostart Directories
	# only the file under the most important directory should be used.
	#
	# Example: If $XDG_CONFIG_HOME is not set the Autostart Directory in the
	# user's home directory is ~/.config/autostart/
	#
	# Example: If $XDG_CONFIG_DIRS is not set the system wide Autostart
	# Directory is /etc/xdg/autostart/
	#
	# Example: If $XDG_CONFIG_HOME and $XDG_CONFIG_DIRS are not set and the
	# two files /etc/xdg/autostart/foo.desktop and
	# ~/.config/autostart/foo.desktop exist then only the file
	# ~/.config/autostart/foo.desktop will be used because
	# ~/.config/autostart/ is more important than /etc/xdg/autostart/
	echo "${XDG_CONFIG_HOME:-$HOME/.config}:${XDG_CONFIG_DIRS:-/etc/xdg}" | tr : \\n |
		sed 's,$,/autostart,' |
		while read -r dir; do
			if test -d "$dir"; then
				for desktop in "$dir"/*.desktop; do
					if ! grep -q "$desktop" "$FOUND_FILE"; then
						auto_start "$desktop" &&
							# `echo' here is
							# important; it /needs/
							# to be output to the
							# $FOUND_FILE
							echo "$desktop" >>"$FOUND_FILE"
					else
						eprint "Already executed: $desktop"
					fi
				done
			fi
		done
}

## Convenience functions

get() { # get KEY FILE
	grep "^$1" "${2:-}" 2>/dev/null | cut -d= -f2-
}

print() {
	$QUIET || printf '%s\n' "$*"
}

eprint() {
	print "$@" >&2
}

die() {
	errcode="$1"
	shift
	eprint "! $*"
	echo "$errcode"
	exit "$errcode"
}

## Entry point

test "$DEBUG" && set -x
main "$@"