ADDED newtab.c Index: newtab.c ================================================================== --- newtab.c +++ newtab.c @@ -0,0 +1,151 @@ +/* [ʞ] newtab.c + * ~ lexi hale + * $ cc -Ofast newtab.c -onewtab \ + * [-D_default_qutebrowser_location=/...] + * $ ./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 + * execs a new qutebrowser instance. + * + * 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 +#include +#include +#include +#include +#include +#include +#define _POSIX_C_SOURCE 200809L +#include + +#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 (ent -> d_name[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()) { + 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; + } +}