commit 4f4c4cb10d48251b46c8a9c81730dceaa5f3e756
Author: Daniel Moch <daniel@danielmoch.com>
Date: Sat, 20 Jun 2020 17:44:43 -0400
Initial commit
Diffstat:
4 files changed, 179 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+xhotplug
diff --git a/Makefile b/Makefile
@@ -0,0 +1,20 @@
+include config.mk
+
+all: xhotplug
+
+.c.o:
+ ${CC} ${CFLAGS} ${CPPFLAGS} -o $@ $<
+
+xhotplug: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+install: xhotplug
+ install -Dm755 xhotplug ${DESTDIR}${PREFIX}/bin/xhotplug
+
+uninstall:
+ -rm -f ${DESTDIR}${PREFIX}/bin/xhotplug
+
+clean:
+ -rm *.o xhotplug
+
+.PHONY: all clean install uninstall
diff --git a/config.mk b/config.mk
@@ -0,0 +1,12 @@
+PREFIX := /usr/local
+MANPATH := ${PREFIX}/share/man
+X11BASE := /usr/X11R6
+
+SRC = xhotplug.c
+OBJ = ${SRC:.c=.o}
+INCS = -I${X11BASE}/include
+LIBS = -L${X11BASE}/lib -lxcb -lxcb-randr
+
+CPPFLAGS := -DVERSION=\"0.1.0dev0\"
+CFLAGS := -std=c99 -pedantic-errors -Wall -Wextra -Werror -O2 -c ${INCS}
+LDFLAGS := ${LIBS}
diff --git a/xhotplug.c b/xhotplug.c
@@ -0,0 +1,146 @@
+/*
+ * Compile with -lxcb -lxcb-randr
+ */
+
+#include <errno.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/syslimits.h>
+#include <unistd.h>
+#include <xcb/xcb.h>
+#include <xcb/randr.h>
+
+void
+usage(const char *argv, const int e_val) {
+ printf("usage: %s [-h] [-v] script\n", argv);
+ exit(e_val);
+}
+
+void
+xhotplug(const char *cmd) {
+ xcb_connection_t* conn;
+ xcb_screen_t* screen;
+ xcb_window_t window;
+ xcb_generic_event_t* evt;
+ xcb_randr_screen_change_notify_event_t* randr_evt;
+ xcb_timestamp_t last_time;
+ int monitor_connected = 1;
+
+ conn = xcb_connect(NULL, NULL);
+
+ screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
+ window = screen->root;
+ xcb_randr_select_input(conn, window, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
+ xcb_flush(conn);
+
+ while ((evt = xcb_wait_for_event(conn)) != NULL) {
+ if (evt->response_type & XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE) {
+ randr_evt = (xcb_randr_screen_change_notify_event_t*) evt;
+ if (last_time != randr_evt->timestamp) {
+ last_time = randr_evt->timestamp;
+ monitor_connected = !monitor_connected;
+ if (monitor_connected) {
+ system(cmd);
+ } else {
+ system(cmd);
+ }
+ }
+ }
+ free(evt);
+ }
+ xcb_disconnect(conn);
+}
+
+int
+can_execute(const char *bname, const char *path) {
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+ gid_t gidset[NGROUPS_MAX];
+ struct stat sb;
+
+ if (stat(path, &sb) != 0) {
+ fprintf(stderr, "%s: ERROR -- Unable to stat file %s\n", bname, path);
+ fprintf(stderr, "%s: errno -- %d\n", bname, errno);
+ return 0;
+ }
+ if (sb.st_uid != uid && sb.st_uid != 0) {
+ fprintf(stderr, "%s: script not owned by user or root\n", bname);
+ return 0;
+ }
+ if (sb.st_uid == uid && (sb.st_mode & S_IXUSR) == 0) {
+ fprintf(stderr, "%s: script is not executable\n", bname);
+ return 0;
+ }
+ if (sb.st_uid == 0) {
+ for (int i=0; i<getgroups(NGROUPS_MAX, gidset); i++) {
+ if (gidset[i] == gid) {
+ if ((sb.st_mode & S_IXGRP) == 0) {
+ fprintf(stderr, "%s: script is not executable\n", bname);
+ return 0;
+ }
+ else {
+ return 1;
+ }
+ }
+ }
+ }
+ if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
+ fprintf(stderr, "%s: script is writeable by others\n", bname);
+ return 0;
+ }
+ return 1;
+}
+
+int
+main(int argc, char **argv) {
+ char ch, *bname = basename(argv[0]), script[PATH_MAX], sbuf[PATH_MAX];
+ ssize_t sbuf_len;
+ int pval;
+
+ while ((ch = getopt(argc, argv, "vh")) != -1) {
+ switch (ch) {
+ case 'v':
+ printf("%s %s\n", bname, VERSION);
+ exit(0);
+ break;
+ case 'h':
+ usage(bname, 0);
+ break;
+ default:
+ usage(bname, 1);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if ( argc < 1 || argc > 1) {
+ printf("%s: invalid number of arguments\n", bname);
+ usage(bname, 2);
+ }
+
+ sbuf_len = strnlen(argv[0], PATH_MAX);
+ strncpy(script, argv[0], PATH_MAX - 1);
+ script[PATH_MAX - 1] = '\0';
+ while ((sbuf_len = readlink(script, sbuf, PATH_MAX)) != -1) {
+ sbuf[sbuf_len] = '\0';
+ strncpy(script, sbuf, PATH_MAX - 1);
+ script[PATH_MAX - 1] = '\0';
+ }
+
+ if(!can_execute(bname, script)) {
+ fprintf(stderr, "%s: cannot run provided script. quitting.\n", bname);
+ usage(bname, 3);
+ }
+
+#ifdef __OpenBSD__
+ if ((pval = pledge("stdio unix rpath proc exec", NULL)) != 0) {
+ fprintf(stderr, "%s: call to pledge(2) failed -- %d. quitting.\n", bname, pval);
+ exit(6);
+ }
+#endif
+
+ xhotplug(script);
+}