Skip to content

Commit

Permalink
koekeishiya#1619 implement launchd integration
Browse files Browse the repository at this point in the history
  • Loading branch information
koekeishiya authored and shinyquagsire23 committed Jun 6, 2023
1 parent 6347b4c commit 479da01
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Added launch arguments to manage launchd service: `--install-service`, `--uninstall-service`, `--start-service`, `--restart-service`, `--stop-service` [#1619](https://github.com/koekeishiya/yabai/issues/1619)

## [5.0.3] - 2023-03-28
### Changed
Expand Down
6 changes: 3 additions & 3 deletions doc/yabai.1
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
.\" Title: yabai
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.16
.\" Date: 2022-12-28
.\" Date: 2023-04-14
.\" Manual: Yabai Manual
.\" Source: Yabai
.\" Language: English
.\"
.TH "YABAI" "1" "2022-12-28" "Yabai" "Yabai Manual"
.TH "YABAI" "1" "2023-04-14" "Yabai" "Yabai Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
Expand All @@ -32,7 +32,7 @@
yabai
.SH "SYNOPSIS"
.sp
\fByabai\fP [\fB\-v\fP,\fB\-\-version\fP|\fB\-V\fP,\fB\-\-verbose\fP|\fB\-m\fP,\fB\-\-message\fP \fImsg\fP|\fB\-c\fP,\fB\-\-config\fP \fIconfig_file\fP|\fB\-\-uninstall\-sa\fP|\fB\-\-load\-sa\fP]
\fByabai\fP [\fB\-v\fP,\fB\-\-version\fP|\fB\-V\fP,\fB\-\-verbose\fP|\fB\-m\fP,\fB\-\-message\fP \fImsg\fP|\fB\-c\fP,\fB\-\-config\fP \fIconfig_file\fP|\fB\-\-uninstall\-sa\fP|\fB\-\-load\-sa\fP|\fB\-\-install\-service\fP|\fB\-\-uninstall\-service\fP|\fB\-\-start\-service\fP|\fB\-\-restart\-service\fP|\fB\-\-stop\-service\fP]
.SH "DESCRIPTION"
.sp
\fByabai\fP is a tiling window manager for macOS based on binary space partitioning.
Expand Down
2 changes: 1 addition & 1 deletion doc/yabai.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ yabai
Synopsis
--------

*yabai* [*-v*,*--version*|*-V*,*--verbose*|*-m*,*--message* 'msg'|*-c*,*--config* 'config_file'|*--uninstall-sa*|*--load-sa*]
*yabai* [*-v*,*--version*|*-V*,*--verbose*|*-m*,*--message* 'msg'|*-c*,*--config* 'config_file'|*--uninstall-sa*|*--load-sa*|*--install-service*|*--uninstall-service*|*--start-service*|*--restart-service*|*--stop-service*]

Description
-----------
Expand Down
2 changes: 2 additions & 0 deletions src/manifest.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <semaphore.h>
#include <pthread.h>
#include <pwd.h>
#include <spawn.h>

#include "misc/extern.h"
#include "misc/macros.h"
Expand All @@ -40,6 +41,7 @@
#define HASHTABLE_IMPLEMENTATION
#include "misc/hashtable.h"
#undef HASHTABLE_IMPLEMENTATION
#include "misc/service.h"

#include "osax/common.h"

Expand Down
162 changes: 162 additions & 0 deletions src/misc/service.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#ifndef SERVICE_H
#define SERVICE_H

#define _NAME_YABAI_PLIST "com.koekeishiya.yabai"
#define _PATH_YABAI_PLIST "/Users/%s/Library/LaunchAgents/"_NAME_YABAI_PLIST".plist"
#define _PATH_LAUNCHCTL "/bin/launchctl"

#define _YABAI_PLIST \
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" \
"<plist version=\"1.0\">\n" \
"<dict>\n" \
" <key>Label</key>\n" \
" <string>"_NAME_YABAI_PLIST"</string>\n" \
" <key>ProgramArguments</key>\n" \
" <array>\n" \
" <string>%s</string>\n" \
" </array>\n" \
" <key>EnvironmentVariables</key>\n" \
" <dict>\n" \
" <key>PATH</key>\n" \
" <string>%s</string>\n" \
" </dict>\n" \
" <key>RunAtLoad</key>\n" \
" <true/>\n" \
" <key>KeepAlive</key>\n" \
" <true/>\n" \
" <key>StandardOutPath</key>\n" \
" <string>/tmp/yabai.out.log</string>\n" \
" <key>StandardErrorPath</key>\n" \
" <string>/tmp/yabai.err.log</string>\n" \
" <key>ThrottleInterval</key>\n" \
" <integer>30</integer>\n" \
" <key>ProcessType</key>\n" \
" <string>Interactive</string>\n" \
" <key>Nice</key>\n" \
" <integer>-20</integer>\n" \
"</dict>\n" \
"</plist>"

static int safe_exec(char *const argv[])
{
pid_t pid;
int status = posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL);
if (status) return 1;

while ((waitpid(pid, &status, 0) == -1) && (errno == EINTR)) {
usleep(1000);
}

if (WIFSIGNALED(status)) {
return 1;
} else if (WIFSTOPPED(status)) {
return 1;
} else {
return WEXITSTATUS(status);
}
}

static void populate_plist_path(char *yabai_plist_path, int size)
{
char *user = getenv("USER");
if (!user) {
error("yabai: 'env USER' not set! abort..\n");
}

snprintf(yabai_plist_path, size, _PATH_YABAI_PLIST, user);
}

static void populate_plist(char *yabai_plist, int size)
{
char *path_env = getenv("PATH");
if (!path_env) {
error("yabai: 'env PATH' not set! abort..\n");
}

char exe_path[4096];
unsigned int exe_path_size = sizeof(exe_path);
if (_NSGetExecutablePath(exe_path, &exe_path_size) < 0) {
error("yabai: unable to retrieve path of executable! abort..\n");
}

snprintf(yabai_plist, size, _YABAI_PLIST, exe_path, path_env);
}

static int service_install(void)
{
char yabai_plist_path[MAXLEN];
populate_plist_path(yabai_plist_path, sizeof(yabai_plist_path));

if (file_exists(yabai_plist_path)) {
error("yabai: service file '%s' is already installed! abort..\n", yabai_plist_path);
}

char yabai_plist[4096];
populate_plist(yabai_plist, sizeof(yabai_plist));

FILE *handle = fopen(yabai_plist_path, "w");
if (!handle) return 1;

size_t bytes = fwrite(yabai_plist, strlen(yabai_plist), 1, handle);
int result = bytes == 1 ? 0 : 1;
fclose(handle);

return result;
}

static int service_uninstall(void)
{
char yabai_plist_path[MAXLEN];
populate_plist_path(yabai_plist_path, sizeof(yabai_plist_path));

if (!file_exists(yabai_plist_path)) {
error("yabai: service file '%s' is not installed! abort..\n", yabai_plist_path);
}

return unlink(yabai_plist_path) == 0 ? 0 : 1;
}

static int service_start(void)
{
char yabai_plist_path[MAXLEN];
populate_plist_path(yabai_plist_path, sizeof(yabai_plist_path));

if (!file_exists(yabai_plist_path)) {
error("yabai: service file '%s' is not installed! abort..\n", yabai_plist_path);
}

const char *const args[] = { _PATH_LAUNCHCTL, "load", "-w", yabai_plist_path, NULL };
return safe_exec((char *const*)args);
}

static int service_restart(void)
{
char yabai_plist_path[MAXLEN];
populate_plist_path(yabai_plist_path, sizeof(yabai_plist_path));

if (!file_exists(yabai_plist_path)) {
error("yabai: service file '%s' is not installed! abort..\n", yabai_plist_path);
}

char yabai_service_id[MAXLEN];
snprintf(yabai_service_id, sizeof(yabai_service_id), "gui/%d/%s", getuid(), _NAME_YABAI_PLIST);

const char *const args[] = { _PATH_LAUNCHCTL, "kickstart", "-k", yabai_service_id, NULL };
return safe_exec((char *const*)args);
}

static int service_stop(void)
{
char yabai_plist_path[MAXLEN];
populate_plist_path(yabai_plist_path, sizeof(yabai_plist_path));

if (!file_exists(yabai_plist_path)) {
error("yabai: service file '%s' is not installed! abort..\n", yabai_plist_path);
}

const char *const args[] = { _PATH_LAUNCHCTL, "unload", "-w", yabai_plist_path, NULL };
return safe_exec((char *const*)args);
}

#endif
26 changes: 26 additions & 0 deletions src/yabai.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
#define SCRPT_ADD_UNINSTALL_OPT "--uninstall-sa"
#define SCRPT_ADD_LOAD_OPT "--load-sa"

#define SERVICE_INSTALL_OPT "--install-service"
#define SERVICE_UNINSTALL_OPT "--uninstall-service"
#define SERVICE_START_OPT "--start-service"
#define SERVICE_RESTART_OPT "--restart-service"
#define SERVICE_STOP_OPT "--stop-service"

#define MAJOR 5
#define MINOR 0
#define PATCH 3
Expand Down Expand Up @@ -246,6 +252,26 @@ static void parse_arguments(int argc, char **argv)
exit(scripting_addition_load());
}

if (string_equals(argv[1], SERVICE_INSTALL_OPT)) {
exit(service_install());
}

if (string_equals(argv[1], SERVICE_UNINSTALL_OPT)) {
exit(service_uninstall());
}

if (string_equals(argv[1], SERVICE_START_OPT)) {
exit(service_start());
}

if (string_equals(argv[1], SERVICE_RESTART_OPT)) {
exit(service_restart());
}

if (string_equals(argv[1], SERVICE_STOP_OPT)) {
exit(service_stop());
}

for (int i = 1; i < argc; ++i) {
char *opt = argv[i];

Expand Down

0 comments on commit 479da01

Please sign in to comment.