util  newtab.c at [46da53de26]

File newtab.c artifact ef6aaa6ad1 part of check-in 46da53de26


/* [ʞ] newtab.c
 *  ~ lexi hale <lexi@hale.su>
 *  $ cc -Ofast newtab.c -onewtab \
 *      [-D_default_qutebrowser_location=/...] \
 *      [-D_enable_vblank]
 *  $ ./newtab [example.net]
 *  © AGPLv3
 *  ? may god have mercy on my soul.
 *    i wrote this because qutebrowser, being a python
 *    abomination, takes an absurdly fucking long time
 *    to load, even when there's already an instance
 *    running and i just want a new goddamn tab. it
 *    turns out, qutebrowser publishes an inane IPC
 *    mechanism, in which JSON (i know!!) written to a
 *    unix domain socket (I KNOW) can send signals to
 *    a running qutebrowser process. newtab checks if
 *    there's a running qutebrowser process and sends
 *    the appropriate IPC signal if so. otherwise, it
 *    forks off a new qutebrowser instance and returns
 *    an exit status of 1.
 *
 *    it takes a single argument, the URI of a page to
 *    navigate to. if launched without an argument,
 *    newtab simply opens a blank new tab. simple
 *    enough, right? but why write it in C? to talk to
 *    a goddammned python application, of all things?
 *    well, i was originally going to do this in a
 *    bash script with socat, but then i pulled up the
 *    socat manpage and immediately decided it would
 *    be faster to implement the damn thing in C, and
 *    performance would be better anyway. i think it
 *    is fair to say that i was correct.
 *
 *    so, that's half the story. the code quality is
 *    the other half. i'm going to be straight with
 *    you: i wrote this sleep-deprived at 7am because
 *    i'd stayed up all night with my boyfriend who's
 *    still remote-working on German time because his
 *    employers are kind of assholes. i'm aware that's
 *    not an excuse and i apologize sincerely. if
 *    anyone wants to submit a MR to tidy up this
 *    abomination without sacrificing performance, i
 *    would welcome it gratefully. */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <dirent.h>
#include <unistd.h>
#define _POSIX_C_SOURCE 200809L
#include <string.h>

#define ssz(str) (str), (sizeof str)
#define dupl(x) x,x

#ifndef _default_qutebrowser_location
#	define _default_qutebrowser_location "/usr/bin/qutebrowser"
#endif

#define jstr(val) "\"" val "\""
#define j(key, val) jstr(#key) ": " val
#define json_msg_start "{" \
			j(protocol_version, "1") "," \
			j(target_arg, jstr("")) "," \
			jstr("args")": [\""
#define json_msg_end "\"]}\n"

enum status {
	ok,
	start_instance,
	fail_xdg_null,
	fail_find,
};

const char* errors [] = {
	[ok] = NULL,
	[start_instance] = NULL,
	[fail_xdg_null] = "$XDG_RUNTIME_DIR is not set - cannot locate IPC socket",
	[fail_find] = "cannot find qutebrowser - is your $PATH set correctly?",
};

enum status
transmit(const char* uri) {
	const char* const run = getenv("XDG_RUNTIME_DIR");
	if (run == NULL) return fail_xdg_null;

	struct sockaddr_un srv = {
		.sun_family = AF_UNIX,
	} ; {
		/* fuck this fuck this fuck this fuck this */
		char* end = stpncpy(srv.sun_path, run, sizeof srv.sun_path);
		end = stpncpy(end, ssz("/qutebrowser/"));
		DIR* qb = opendir(srv.sun_path);
		if (!qb) return start_instance;
		struct dirent* ent;
		while ((ent = readdir(qb))) {
			if (ent == NULL) return start_instance;
			if (strncmp(ent -> d_name, "ipc-", 4) == 0) break;
		}
		if (ent == NULL) return start_instance;
		end = stpncpy(end, ent->d_name,
				(sizeof srv.sun_path) - (end - srv.sun_path));
		closedir(qb);
	}

	int sock = socket(AF_UNIX, SOCK_STREAM, 0);
	if (connect(sock, (struct sockaddr*)&srv, sizeof srv) != 0)
		return start_instance;

	const char msg_blank [] = json_msg_start json_msg_end;
	const size_t extra = uri != NULL ? strlen(uri) : 0;
	char msg_start [(sizeof json_msg_start) + extra];

	const size_t msgsz = sizeof msg_blank + extra - 1;
	const char*  msg;
	if (uri == NULL) msg = msg_blank; else {
		strcpy(msg_start, json_msg_start);
		strcpy(msg_start + (sizeof json_msg_start) - 1, uri);
		strcpy(msg_start + (sizeof json_msg_start) - 1 + extra, json_msg_end);
		msg = msg_start;
	}

	const char* cur = msg;
	while(cur < msg + msgsz)
		cur += write(sock, cur, msgsz - (cur - msg));

	close(sock);

	return ok;
}

int main(int argc, char** argv) {
	if (argc > 2) {
		printf("\x1b[1musage:\x1b[m %s [uri]\n", argv[0]);
	} else {
		const char* uri = argc < 2 ? NULL : argv[1];
		enum status st = transmit(uri);
		if (st == start_instance) {
			if (!fork()) {
#				ifndef _enable_vblank
					setenv("vblank_mode","0",0);
#				endif
				execl(dupl(_default_qutebrowser_location), uri, NULL);
				execl(dupl("/usr/local/bin/qutebrowser"), uri, NULL);
				execlp(dupl("qutebrowser"), uri, NULL);
				st = fail_find;
			} else {
				return start_instance;
			}
		}
		if (errors[st] != NULL) printf("\x1b[1;31m(error)\x1b[m %s\n", errors[st]);
		return st;
	}
}