aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkoekeishiya <aasvi93@hotmail.com>2023-05-27 20:02:07 +0200
committerkoekeishiya <aasvi93@hotmail.com>2023-05-27 20:02:07 +0200
commit8001c9ad537d43eb587c5a325296bdaebd565708 (patch)
tree5c50636e6e92ec5102ca1aae3ab71dd2845f3675
parentb06f2332e7eb08e2132d36462af759ed5da94ab4 (diff)
downloadskhd-8001c9ad537d43eb587c5a325296bdaebd565708.tar.gz
skhd-8001c9ad537d43eb587c5a325296bdaebd565708.zip
improve launchd integration
-rw-r--r--src/log.h10
-rw-r--r--src/service.h128
-rw-r--r--src/skhd.c4
3 files changed, 123 insertions, 19 deletions
diff --git a/src/log.h b/src/log.h
index 2e8167b..d3cf6f1 100644
--- a/src/log.h
+++ b/src/log.h
@@ -33,4 +33,14 @@ error(const char *format, ...)
exit(EXIT_FAILURE);
}
+static inline void
+require(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ exit(EXIT_SUCCESS);
+}
+
#endif
diff --git a/src/service.h b/src/service.h
index bb43e8e..ae97084 100644
--- a/src/service.h
+++ b/src/service.h
@@ -29,13 +29,16 @@
" <key>RunAtLoad</key>\n" \
" <true/>\n" \
" <key>KeepAlive</key>\n" \
- " <true/>\n" \
+ " <dict>\n" \
+ " <key>SuccessfulExit</key>\n" \
+ " <false/>\n" \
+ " <key>Crashed</key>\n" \
+ " <true/>\n" \
+ " </dict>\n" \
" <key>StandardOutPath</key>\n" \
" <string>/tmp/skhd_%s.out.log</string>\n" \
" <key>StandardErrorPath</key>\n" \
" <string>/tmp/skhd_%s.err.log</string>\n" \
- " <key>ThrottleInterval</key>\n" \
- " <integer>30</integer>\n" \
" <key>ProcessType</key>\n" \
" <string>Interactive</string>\n" \
" <key>Nice</key>\n" \
@@ -43,12 +46,27 @@
"</dict>\n" \
"</plist>"
-static bool file_exists(char *filename);
+//
+// NOTE(koekeishiya): A launchd service has the following states:
+//
+// 1. Installed / Uninstalled
+// 2. Active (Enable / Disable)
+// 3. Bootstrapped (Load / Unload)
+// 4. Running (Start / Stop)
+//
-static int safe_exec(char *const argv[])
+static int safe_exec(char *const argv[], bool suppress_output)
{
pid_t pid;
- int status = posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL);
+ posix_spawn_file_actions_t actions;
+ posix_spawn_file_actions_init(&actions);
+
+ if (suppress_output) {
+ posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, "/dev/null", O_WRONLY|O_APPEND, 0);
+ posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null", O_WRONLY|O_APPEND, 0);
+ }
+
+ int status = posix_spawn(&pid, argv[0], &actions, NULL, argv, NULL);
if (status) return 1;
while ((waitpid(pid, &status, 0) == -1) && (errno == EINTR)) {
@@ -183,6 +201,8 @@ static int service_install_internal(char *skhd_plist_path)
return result;
}
+static bool file_exists(char *filename);
+
static int service_install(void)
{
char *skhd_plist_path = populate_plist_path();
@@ -208,7 +228,6 @@ static int service_uninstall(void)
static int service_start(void)
{
char *skhd_plist_path = populate_plist_path();
-
if (!file_exists(skhd_plist_path)) {
warn("skhd: service file '%s' is not installed! attempting installation..\n", skhd_plist_path);
@@ -218,35 +237,110 @@ static int service_start(void)
}
}
- const char *const args[] = { _PATH_LAUNCHCTL, "load", "-w", skhd_plist_path, NULL };
- return safe_exec((char *const*)args);
+ char service_target[MAXLEN];
+ snprintf(service_target, sizeof(service_target), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST);
+
+ char domain_target[MAXLEN];
+ snprintf(domain_target, sizeof(domain_target), "gui/%d", getuid());
+
+ //
+ // NOTE(koekeishiya): Check if service is bootstrapped
+ //
+
+ const char *const args[] = { _PATH_LAUNCHCTL, "print", service_target, NULL };
+ int is_bootstrapped = safe_exec((char *const*)args, true);
+
+ if (is_bootstrapped != 0) {
+
+ //
+ // NOTE(koekeishiya): Service is not bootstrapped and could be disabled.
+ // There is no way to query if the service is disabled, and we cannot
+ // bootstrap a disabled service. Try to enable the service. This will be
+ // a no-op if the service is already enabled.
+ //
+
+ const char *const args[] = { _PATH_LAUNCHCTL, "enable", service_target, NULL };
+ safe_exec((char *const*)args, false);
+
+ //
+ // NOTE(koekeishiya): Bootstrap service into the target domain.
+ // This will also start the program **iff* RunAtLoad is set to true.
+ //
+
+ const char *const args2[] = { _PATH_LAUNCHCTL, "bootstrap", domain_target, skhd_plist_path, NULL };
+ return safe_exec((char *const*)args2, false);
+ } else {
+
+ //
+ // NOTE(koekeishiya): The service has already been bootstrapped.
+ // Tell the bootstrapped service to launch immediately; it is an
+ // error to bootstrap a service that has already been bootstrapped.
+ //
+
+ const char *const args[] = { _PATH_LAUNCHCTL, "kickstart", service_target, NULL };
+ return safe_exec((char *const*)args, false);
+ }
}
static int service_restart(void)
{
char *skhd_plist_path = populate_plist_path();
-
if (!file_exists(skhd_plist_path)) {
error("skhd: service file '%s' is not installed! abort..\n", skhd_plist_path);
}
- char skhd_service_id[MAXLEN];
- snprintf(skhd_service_id, sizeof(skhd_service_id), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST);
+ char service_target[MAXLEN];
+ snprintf(service_target, sizeof(service_target), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST);
- const char *const args[] = { _PATH_LAUNCHCTL, "kickstart", "-k", skhd_service_id, NULL };
- return safe_exec((char *const*)args);
+ const char *const args[] = { _PATH_LAUNCHCTL, "kickstart", "-k", service_target, NULL };
+ return safe_exec((char *const*)args, false);
}
static int service_stop(void)
{
char *skhd_plist_path = populate_plist_path();
-
if (!file_exists(skhd_plist_path)) {
error("skhd: service file '%s' is not installed! abort..\n", skhd_plist_path);
}
- const char *const args[] = { _PATH_LAUNCHCTL, "unload", "-w", skhd_plist_path, NULL };
- return safe_exec((char *const*)args);
+ char service_target[MAXLEN];
+ snprintf(service_target, sizeof(service_target), "gui/%d/%s", getuid(), _NAME_SKHD_PLIST);
+
+ char domain_target[MAXLEN];
+ snprintf(domain_target, sizeof(domain_target), "gui/%d", getuid());
+
+ //
+ // NOTE(koekeishiya): Check if service is bootstrapped
+ //
+
+ const char *const args[] = { _PATH_LAUNCHCTL, "print", service_target, NULL };
+ int is_bootstrapped = safe_exec((char *const*)args, true);
+
+ if (is_bootstrapped != 0) {
+
+ //
+ // NOTE(koekeishiya): Service is not bootstrapped, but the program
+ // could still be running an instance that was started **while the service
+ // was bootstrapped**, so we tell it to stop said service.
+ //
+
+ const char *const args[] = { _PATH_LAUNCHCTL, "kill", "SIGTERM", service_target, NULL };
+ return safe_exec((char *const*)args, false);
+ } else {
+
+ //
+ // NOTE(koekeishiya): Service is bootstrapped; we stop a potentially
+ // running instance of the program and unload the service, making it
+ // not trigger automatically in the future.
+ //
+ // This is NOT the same as disabling the service, which will prevent
+ // it from being boostrapped in the future (without explicitly re-enabling
+ // it first).
+ //
+
+ const char *const args[] = { _PATH_LAUNCHCTL, "bootout", domain_target, skhd_plist_path, NULL };
+ return safe_exec((char *const*)args, false);
+ }
}
#endif
diff --git a/src/skhd.c b/src/skhd.c
index 6da4456..b8f5c2f 100644
--- a/src/skhd.c
+++ b/src/skhd.c
@@ -459,7 +459,7 @@ static GLOBAL_CONNECTION_CALLBACK(connection_handler)
int main(int argc, char **argv)
{
if (getuid() == 0 || geteuid() == 0) {
- error("skhd: running as root is not allowed! abort..\n");
+ require("skhd: running as root is not allowed! abort..\n");
}
if (parse_arguments(argc, argv)) {
@@ -475,7 +475,7 @@ int main(int argc, char **argv)
}
if (!check_privileges()) {
- error("skhd: must be run with accessibility access! abort..\n");
+ require("skhd: must be run with accessibility access! abort..\n");
}
if (!initialize_keycode_map()) {