summaryrefslogtreecommitdiffstats
path: root/src/daemon.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/daemon.c')
-rw-r--r--src/daemon.c847
1 files changed, 353 insertions, 494 deletions
diff --git a/src/daemon.c b/src/daemon.c
index 82154db..8f73682 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -13,7 +13,6 @@
#include <stdlib.h>
#include <string.h>
#include <strings.h>
-#include <sys/inotify.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
@@ -24,7 +23,7 @@
#define MAX_BINDS 512
#define MAX_CMD_LEN 1024
-#define SOCK_PATH "/tmp/piss.sock"
+#define SOCK_PATH "/tmp/chd.sock"
#define MOD_SHIFT XCB_MOD_MASK_SHIFT
#define MOD_CAPS XCB_MOD_MASK_LOCK
@@ -36,600 +35,460 @@
#define CLEAN_MASK (~(MOD_NUMLOCK | MOD_CAPS))
typedef struct {
- uint16_t mod;
- xcb_keycode_t keycode;
- char cmd[MAX_CMD_LEN];
+ uint16_t mod;
+ xcb_keycode_t keycode;
+ char cmd[MAX_CMD_LEN];
} Binding;
-static xcb_connection_t *conn = NULL;
-static xcb_window_t root = 0;
+static xcb_connection_t *conn;
+static xcb_window_t root;
-static xcb_get_keyboard_mapping_reply_t *kbmap = NULL;
-static xcb_keycode_t kbmap_min = 0;
-static xcb_keycode_t kbmap_max = 0;
+static xcb_get_keyboard_mapping_reply_t *kbmap;
+static xcb_keycode_t kbmap_min;
+static xcb_keycode_t kbmap_max;
static Binding bindings[MAX_BINDS];
-static int bind_count = 0;
+static int bind_count;
-static volatile sig_atomic_t g_quit = 0;
+static volatile sig_atomic_t g_quit;
+static volatile sig_atomic_t g_reload;
+static volatile sig_atomic_t g_restart;
static char g_cfg[512];
+static char **g_argv;
-static void die(const char *fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- fprintf(stderr, "piss: fatal: ");
- vfprintf(stderr, fmt, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
+static void die(const char *msg) {
+ fprintf(stderr, "chd: %s\n", msg);
+ exit(1);
}
-static void warn(const char *fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- fprintf(stderr, "piss: warning: ");
- vfprintf(stderr, fmt, ap);
- va_end(ap);
- fputc('\n', stderr);
+static void die_errno(const char *msg) {
+ fprintf(stderr, "chd: %s: %s\n", msg, strerror(errno));
+ exit(1);
}
static void handle_signal(int sig) {
- (void)sig;
- g_quit = 1;
+ if (sig == SIGTERM || sig == SIGINT)
+ g_quit = 1;
+ if (sig == SIGUSR1)
+ g_reload = 1;
+ if (sig == SIGUSR2)
+ g_restart = 1;
}
static void refresh_keymap(void) {
- free(kbmap);
- xcb_get_keyboard_mapping_cookie_t ck = xcb_get_keyboard_mapping(
- conn, kbmap_min, (uint8_t)(kbmap_max - kbmap_min + 1));
- xcb_generic_error_t *e = NULL;
- kbmap = xcb_get_keyboard_mapping_reply(conn, ck, &e);
- if (e) {
- int code = e->error_code;
- free(e);
- die("xcb_get_keyboard_mapping error %d", code);
- }
- if (!kbmap)
- die("xcb_get_keyboard_mapping returned NULL");
+ free(kbmap);
+ xcb_get_keyboard_mapping_cookie_t ck = xcb_get_keyboard_mapping(
+ conn, kbmap_min, (uint8_t)(kbmap_max - kbmap_min + 1));
+ xcb_generic_error_t *e = NULL;
+ kbmap = xcb_get_keyboard_mapping_reply(conn, ck, &e);
+ if (e) {
+ fprintf(stderr, "chd: keyboard mapping failed (error %d)\n", e->error_code);
+ free(e);
+ }
+ if (!kbmap)
+ die("could not retrieve keyboard mapping");
}
static xcb_keycode_t keysym_to_keycode(xcb_keysym_t sym) {
- if (!kbmap)
- return 0;
+ if (!kbmap)
+ return 0;
- const xcb_keysym_t *table = xcb_get_keyboard_mapping_keysyms(kbmap);
- int kpk = kbmap->keysyms_per_keycode;
+ const xcb_keysym_t *table = xcb_get_keyboard_mapping_keysyms(kbmap);
+ int kpk = kbmap->keysyms_per_keycode;
- for (int kc = kbmap_min; kc <= kbmap_max; kc++) {
- const xcb_keysym_t *row = table + (kc - kbmap_min) * kpk;
- for (int col = 0; col < kpk; col++) {
- if (row[col] == sym)
- return (xcb_keycode_t)kc;
+ for (int kc = kbmap_min; kc <= kbmap_max; kc++) {
+ const xcb_keysym_t *row = table + (kc - kbmap_min) * kpk;
+ for (int i = 0; i < kpk; i++)
+ if (row[i] == sym)
+ return (xcb_keycode_t)kc;
}
- }
- return 0;
+ return 0;
}
static uint16_t parse_modifiers(const char *str) {
- uint16_t mod = 0;
- if (strcasestr(str, "super") || strcasestr(str, "mod4"))
- mod |= MOD_SUPER;
- if (strcasestr(str, "shift"))
- mod |= MOD_SHIFT;
- if (strcasestr(str, "ctrl") || strcasestr(str, "control"))
- mod |= MOD_CTRL;
- if (strcasestr(str, "alt") || strcasestr(str, "mod1"))
- mod |= MOD_ALT;
- return mod;
+ uint16_t mod = 0;
+ if (strcasestr(str, "super") || strcasestr(str, "mod4"))
+ mod |= MOD_SUPER;
+ if (strcasestr(str, "shift"))
+ mod |= MOD_SHIFT;
+ if (strcasestr(str, "ctrl") || strcasestr(str, "control"))
+ mod |= MOD_CTRL;
+ if (strcasestr(str, "alt") || strcasestr(str, "mod1"))
+ mod |= MOD_ALT;
+ return mod;
}
static int grab_key(uint16_t mod, xcb_keycode_t kc) {
- uint16_t mods[4] = {
- mod,
- (uint16_t)(mod | MOD_NUMLOCK),
- (uint16_t)(mod | MOD_CAPS),
- (uint16_t)(mod | MOD_NUMLOCK | MOD_CAPS),
- };
- int failed = 0;
- for (int i = 0; i < 4; i++) {
- xcb_void_cookie_t ck = xcb_grab_key_checked(
- conn, 1, root, mods[i], kc, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
- xcb_generic_error_t *e = xcb_request_check(conn, ck);
- if (e) {
- if (e->error_code == XCB_ACCESS)
- warn("keycode %u mod 0x%04x already grabbed by another client"
- " — is another hotkey daemon running?",
- kc, mods[i]);
- else
- warn("XGrabKey error %d for keycode %u mod 0x%04x", e->error_code, kc,
- mods[i]);
- free(e);
- failed = 1;
- }
- }
- return failed ? -1 : 0;
-}
-
-static void ungrab_all(void) {
- for (int i = 0; i < bind_count; i++) {
- uint16_t m = bindings[i].mod;
- xcb_keycode_t kc = bindings[i].keycode;
uint16_t mods[4] = {
- m,
- (uint16_t)(m | MOD_NUMLOCK),
- (uint16_t)(m | MOD_CAPS),
- (uint16_t)(m | MOD_NUMLOCK | MOD_CAPS),
+ mod,
+ (uint16_t)(mod | MOD_NUMLOCK),
+ (uint16_t)(mod | MOD_CAPS),
+ (uint16_t)(mod | MOD_NUMLOCK | MOD_CAPS),
};
- for (int j = 0; j < 4; j++)
- xcb_ungrab_key(conn, kc, root, mods[j]);
- }
- xcb_flush(conn);
-}
-
-static int add_binding(char *msg) {
- if (bind_count >= MAX_BINDS) {
- warn("MAX_BINDS (%d) reached — ignoring", MAX_BINDS);
- return -1;
- }
- char *sep = strstr(msg, "|||");
- if (!sep) {
- warn("malformed bind message (no |||): %.80s", msg);
- return -1;
- }
- *sep = '\0';
- char *combo = msg;
- char *cmd = sep + 3;
-
- if (!*cmd) {
- warn("empty command for combo '%s'", combo);
- return -1;
- }
-
- char *last_plus = strrchr(combo, '+');
- if (!last_plus) {
- warn("no '+' in combo '%s'", combo);
- return -1;
- }
- *last_plus = '\0';
- char *keyname = last_plus + 1;
-
- while (*keyname == ' ' || *keyname == '\t')
- keyname++;
- {
- char *e = keyname + strlen(keyname);
- while (e > keyname && (e[-1] == ' ' || e[-1] == '\t'))
- *--e = '\0';
- }
-
- if (!*keyname) {
- warn("empty key name in combo");
- return -1;
- }
-
- KeySym sym = XStringToKeysym(keyname);
- if (sym == NoSymbol) {
- warn("unknown keysym '%s'", keyname);
- return -1;
- }
-
- xcb_keycode_t kc = keysym_to_keycode((xcb_keysym_t)sym);
- if (kc == 0) {
- warn("no keycode for keysym '%s' (0x%lx)", keyname, sym);
- return -1;
- }
-
- uint16_t mod = parse_modifiers(combo);
-
- for (int i = 0; i < bind_count; i++) {
- if (bindings[i].keycode == kc && bindings[i].mod == mod) {
- warn("overwriting existing binding for '%s+%s'", combo, keyname);
- strncpy(bindings[i].cmd, cmd, MAX_CMD_LEN - 1);
- bindings[i].cmd[MAX_CMD_LEN - 1] = '\0';
- return 0;
+ for (int i = 0; i < 4; i++) {
+ xcb_void_cookie_t ck =
+ xcb_grab_key_checked(conn, 1, root, mods[i], kc,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+ xcb_generic_error_t *e = xcb_request_check(conn, ck);
+ if (e) {
+ fprintf(stderr, "chd: grab_key failed for mod=%u kc=%u (error %d)\n",
+ mods[i], kc, e->error_code);
+ free(e);
+ return -1;
+ }
}
- }
+ return 0;
+}
- bindings[bind_count].mod = mod;
- bindings[bind_count].keycode = kc;
- strncpy(bindings[bind_count].cmd, cmd, MAX_CMD_LEN - 1);
- bindings[bind_count].cmd[MAX_CMD_LEN - 1] = '\0';
+static void ungrab_all(void) {
+ for (int i = 0; i < bind_count; i++) {
+ uint16_t m = bindings[i].mod;
+ xcb_keycode_t kc = bindings[i].keycode;
- if (grab_key(mod, kc) == -1)
- warn("stored binding but grab failed for '%s+%s'", combo, keyname);
+ uint16_t mods[4] = {
+ m,
+ (uint16_t)(m | MOD_NUMLOCK),
+ (uint16_t)(m | MOD_CAPS),
+ (uint16_t)(m | MOD_NUMLOCK | MOD_CAPS),
+ };
- bind_count++;
- xcb_flush(conn);
- fprintf(stderr, "piss: bound '%s+%s' → %s\n", combo, keyname, cmd);
- return 0;
+ for (int j = 0; j < 4; j++)
+ xcb_ungrab_key(conn, kc, root, mods[j]);
+ }
+ xcb_flush(conn);
}
static void spawn(const char *cmd) {
- pid_t pid = fork();
- if (pid < 0) {
- warn("fork: %s", strerror(errno));
- return;
- }
- if (pid == 0) {
- setsid();
- signal(SIGCHLD, SIG_DFL);
- long maxfd = sysconf(_SC_OPEN_MAX);
- if (maxfd < 0)
- maxfd = 1024;
- for (long fd = 3; fd < maxfd; fd++)
- close((int)fd);
- execl("/bin/sh", "sh", "-c", cmd, NULL);
- fprintf(stderr, "piss: execl: %s\n", strerror(errno));
- _exit(1);
- }
+ pid_t pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "chd: fork failed: %s\n", strerror(errno));
+ return;
+ }
+ if (pid == 0) {
+ setsid();
+ execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
+ fprintf(stderr, "chd: execl failed: %s\n", strerror(errno));
+ _exit(1);
+ }
}
-static int make_server_socket(void) {
- int fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (fd < 0)
- die("socket: %s", strerror(errno));
-
- if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
- die("fcntl(CLOEXEC) on server socket: %s", strerror(errno));
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
+static xcb_window_t find_first_window(xcb_window_t w) {
+ xcb_query_tree_cookie_t ck = xcb_query_tree(conn, w);
+ xcb_generic_error_t *e = NULL;
+ xcb_query_tree_reply_t *r = xcb_query_tree_reply(conn, ck, &e);
+ if (e) {
+ free(e);
+ return XCB_NONE;
+ }
+ if (!r)
+ return XCB_NONE;
- unlink(SOCK_PATH);
+ xcb_window_t *children = xcb_query_tree_children(r);
+ int n = xcb_query_tree_children_length(r);
+ xcb_window_t found = XCB_NONE;
- if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
- die("bind(%s): %s", SOCK_PATH, strerror(errno));
+ for (int i = 0; i < n && found == XCB_NONE; i++) {
+ xcb_get_window_attributes_cookie_t ack =
+ xcb_get_window_attributes(conn, children[i]);
+ xcb_get_window_attributes_reply_t *attr =
+ xcb_get_window_attributes_reply(conn, ack, NULL);
- if (listen(fd, 32) < 0)
- die("listen: %s", strerror(errno));
+ if (attr) {
+ if (attr->map_state == XCB_MAP_STATE_VIEWABLE &&
+ attr->_class == XCB_WINDOW_CLASS_INPUT_OUTPUT) {
+ found = children[i];
+ }
+ free(attr);
+ }
- if (chmod(SOCK_PATH, 0600) < 0)
- warn("chmod(%s): %s", SOCK_PATH, strerror(errno));
+ if (found == XCB_NONE)
+ found = find_first_window(children[i]);
+ }
- return fd;
+ free(r);
+ return found;
}
-static int client_mode(int retry, const char *combo, const char *cmd) {
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
-
- for (;;) {
- int sock = socket(AF_UNIX, SOCK_STREAM, 0);
- if (sock < 0) {
- perror("piss: socket");
- return 1;
+static int focus_first_window(void) {
+ xcb_window_t w = find_first_window(root);
+ if (w == XCB_NONE) {
+ fprintf(stderr, "chd: no viewable window found\n");
+ return 1;
}
- if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
- int ret = 0;
- if (combo && cmd) {
- char buf[MAX_CMD_LEN * 2];
- int n = snprintf(buf, sizeof(buf), "%s|||%s", combo, cmd);
- if (n < 0 || (size_t)n >= sizeof(buf)) {
- fprintf(stderr, "piss: bind message too long\n");
- ret = 1;
- } else if (write(sock, buf, (size_t)n) < 0) {
- perror("piss: write");
- ret = 1;
- } else {
- char ack[8] = {0};
- ssize_t nr = read(sock, ack, sizeof(ack) - 1);
- if (nr > 0 && strncmp(ack, "err", 3) == 0) {
- fprintf(stderr, "piss: daemon reported bind error\n");
- ret = 1;
- }
- }
- }
- close(sock);
- return ret;
+ xcb_void_cookie_t ck = xcb_set_input_focus_checked(
+ conn, XCB_INPUT_FOCUS_POINTER_ROOT, w, XCB_CURRENT_TIME);
+ xcb_generic_error_t *e = xcb_request_check(conn, ck);
+ if (e) {
+ fprintf(stderr, "chd: set_input_focus failed (error %d)\n", e->error_code);
+ free(e);
+ return 1;
}
- close(sock);
+ xcb_flush(conn);
+ return 0;
+}
- if (!retry) {
- fprintf(stderr, "piss: cannot connect to %s: %s\n", SOCK_PATH,
- strerror(errno));
- return 1;
+static int add_binding(char *msg) {
+ if (!strncmp(msg, "reload", 6)) {
+ g_reload = 1;
+ return 0;
}
- usleep(50000);
- }
-}
-static void get_default_config(char *buf, size_t len) {
- struct passwd *pw = getpwuid(getuid());
- if (!pw)
- die("getpwuid: %s", strerror(errno));
- int n = snprintf(buf, len, "%s/.config/piss/pissrc", pw->pw_dir);
- if (n < 0 || (size_t)n >= len)
- die("default config path too long");
-}
+ if (!strncmp(msg, "restart", 7)) {
+ g_restart = 1;
+ return 0;
+ }
-static void ensure_executable(const char *path) {
- struct stat st;
- if (stat(path, &st) < 0)
- die("stat(%s): %s\n"
- " (does the config file exist?)",
- path, strerror(errno));
- if (!(st.st_mode & S_IXUSR)) {
- if (chmod(path, st.st_mode | S_IXUSR) < 0)
- die("chmod(%s): %s", path, strerror(errno));
- }
-}
+ if (!strncmp(msg, "focus", 5))
+ return focus_first_window();
-static void reload_config(int inotify_fd, int *watch_wd) {
- fprintf(stderr, "piss: config changed, reloading\n");
+ char *sep = strstr(msg, "|||");
+ if (!sep) {
+ fprintf(stderr, "chd: malformed bind message (missing |||)\n");
+ return -1;
+ }
- ungrab_all();
- bind_count = 0;
+ *sep = 0;
+ char *combo = msg;
+ char *cmd = sep + 3;
- if (*watch_wd >= 0) {
- inotify_rm_watch(inotify_fd, *watch_wd);
- *watch_wd = -1;
- }
+ char *last = strrchr(combo, '+');
+ if (!last) {
+ fprintf(stderr, "chd: malformed combo (no + separator)\n");
+ return -1;
+ }
- ensure_executable(g_cfg);
+ *last = 0;
+ char *key = last + 1;
- *watch_wd =
- inotify_add_watch(inotify_fd, g_cfg, IN_CLOSE_WRITE | IN_MOVED_TO);
- if (*watch_wd < 0)
- warn("inotify_add_watch(%s): %s", g_cfg, strerror(errno));
+ KeySym sym = XStringToKeysym(key);
+ if (sym == NoSymbol) {
+ fprintf(stderr, "chd: unknown key '%s'\n", key);
+ return -1;
+ }
- spawn("notify-send -i input-keyboard 'piss' 'Config reloaded'");
+ xcb_keycode_t kc = keysym_to_keycode(sym);
+ if (!kc) {
+ fprintf(stderr, "chd: no keycode for key '%s'\n", key);
+ return -1;
+ }
- pid_t pid = fork();
- if (pid < 0) {
- warn("fork (reload): %s", strerror(errno));
- return;
- }
- if (pid == 0) {
- char *args[] = {g_cfg, NULL};
- execvp(g_cfg, args);
- fprintf(stderr, "piss: execvp(%s): %s\n", g_cfg, strerror(errno));
- _exit(1);
- }
-}
+ uint16_t mod = parse_modifiers(combo);
-int main(int argc, char *argv[]) {
+ for (int i = 0; i < bind_count; i++) {
+ if (bindings[i].mod == mod && bindings[i].keycode == kc) {
+ strncpy(bindings[i].cmd, cmd, MAX_CMD_LEN - 1);
+ bindings[i].cmd[MAX_CMD_LEN - 1] = '\0';
+ return 0;
+ }
+ }
- if (argc >= 2 && strcmp(argv[1], "wait") == 0)
- return client_mode(1, NULL, NULL);
+ if (bind_count >= MAX_BINDS) {
+ fprintf(stderr, "chd: binding table full (%d max)\n", MAX_BINDS);
+ return -1;
+ }
+
+ bindings[bind_count].mod = mod;
+ bindings[bind_count].keycode = kc;
+ strncpy(bindings[bind_count].cmd, cmd, MAX_CMD_LEN - 1);
+ bindings[bind_count].cmd[MAX_CMD_LEN - 1] = '\0';
- if (argc >= 4 && strcmp(argv[1], "bind") == 0)
- return client_mode(0, argv[2], argv[3]);
+ if (grab_key(mod, kc) < 0)
+ return -1;
- if (argc >= 2 &&
- (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
- fprintf(stderr, "usage:\n"
- " piss [pissrc] start daemon\n"
- " piss wait wait until daemon is ready\n"
- " piss bind \"Mod+Key\" \"cmd\" register a binding\n");
+ bind_count++;
return 0;
- }
+}
- {
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
- sa.sa_handler = SIG_IGN;
- sa.sa_flags = SA_NOCLDWAIT;
- if (sigaction(SIGCHLD, &sa, NULL) < 0)
- die("sigaction(SIGCHLD): %s", strerror(errno));
- }
+static int make_socket(void) {
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ die_errno("socket");
- {
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
- sa.sa_handler = handle_signal;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = 0;
- if (sigaction(SIGTERM, &sa, NULL) < 0)
- die("sigaction(SIGTERM): %s", strerror(errno));
- if (sigaction(SIGINT, &sa, NULL) < 0)
- die("sigaction(SIGINT): %s", strerror(errno));
- }
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
- int screen_num = 0;
- conn = xcb_connect(NULL, &screen_num);
- if (!conn)
- die("xcb_connect returned NULL — is DISPLAY set?");
- {
- int cerr = xcb_connection_has_error(conn);
- if (cerr)
- die("XCB connection error %d — is DISPLAY set correctly?", cerr);
- }
+ unlink(SOCK_PATH);
- {
- const xcb_setup_t *setup = xcb_get_setup(conn);
- xcb_screen_iterator_t it = xcb_setup_roots_iterator(setup);
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+ die_errno("bind");
- kbmap_min = setup->min_keycode;
- kbmap_max = setup->max_keycode;
+ if (listen(fd, 16) < 0)
+ die_errno("listen");
- for (int i = 0; i < screen_num; i++) {
- if (!it.rem)
- die("screen %d does not exist", screen_num);
- xcb_screen_next(&it);
- }
- if (!it.rem)
- die("no screen found");
- root = it.data->root;
- }
+ return fd;
+}
- refresh_keymap();
+static int client(const char *msg) {
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
- {
- uint32_t evmask = XCB_EVENT_MASK_KEY_PRESS;
- xcb_void_cookie_t ck = xcb_change_window_attributes_checked(
- conn, root, XCB_CW_EVENT_MASK, &evmask);
- xcb_generic_error_t *e = xcb_request_check(conn, ck);
- if (e) {
- int code = e->error_code;
- free(e);
- die("ChangeWindowAttributes error %d — "
- "another hotkey daemon or WM may already own the root",
- code);
+ int s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s < 0) {
+ fprintf(stderr, "chd: socket: %s\n", strerror(errno));
+ return 1;
}
- }
-
- int serv = make_server_socket();
- {
- char self[512] = {0};
- ssize_t n = readlink("/proc/self/exe", self, sizeof(self) - 1);
- if (n > 0) {
- self[n] = '\0';
- setenv("PISS_BIN", self, 1);
+ if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ fprintf(stderr, "chd: connect: %s\n", strerror(errno));
+ close(s);
+ return 1;
}
- }
- if (argc > 1) {
- strncpy(g_cfg, argv[1], sizeof(g_cfg) - 1);
- g_cfg[sizeof(g_cfg) - 1] = '\0';
- } else {
- get_default_config(g_cfg, sizeof(g_cfg));
- }
- ensure_executable(g_cfg);
+ ssize_t w = write(s, msg, strlen(msg));
+ if (w < 0)
+ fprintf(stderr, "chd: write: %s\n", strerror(errno));
- int inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
- if (inotify_fd < 0)
- die("inotify_init1: %s", strerror(errno));
+ close(s);
+ return w < 0 ? 1 : 0;
+}
- int watch_wd =
- inotify_add_watch(inotify_fd, g_cfg, IN_CLOSE_WRITE | IN_MOVED_TO);
- if (watch_wd < 0)
- warn("inotify_add_watch(%s): %s — live reload disabled", g_cfg,
- strerror(errno));
+static void get_cfg(void) {
+ struct passwd *pw = getpwuid(getuid());
+ if (!pw)
+ die_errno("getpwuid");
+ snprintf(g_cfg, sizeof(g_cfg), "%s/.config/chd/chdrc", pw->pw_dir);
+}
- xcb_flush(conn);
+int main(int argc, char *argv[]) {
+ g_argv = argv;
- {
- pid_t pid = fork();
- if (pid < 0)
- die("fork: %s", strerror(errno));
- if (pid == 0) {
- char *args[] = {g_cfg, NULL};
- execvp(g_cfg, args);
- fprintf(stderr, "piss: execvp(%s): %s\n", g_cfg, strerror(errno));
- _exit(1);
+ if (argc > 1 && !strcmp(argv[1], "bind")) {
+ if (argc < 3) {
+ fprintf(stderr, "chd: bind requires an argument\n");
+ return 1;
+ }
+ return client(argv[2]);
}
- }
- int xcb_fd = xcb_get_file_descriptor(conn);
+ if (argc > 1 && !strcmp(argv[1], "reload"))
+ return client("reload");
- while (!g_quit) {
- while (waitpid(-1, NULL, WNOHANG) > 0)
- ;
+ if (argc > 1 && !strcmp(argv[1], "restart"))
+ return client("restart");
- fd_set rfds;
- FD_ZERO(&rfds);
- FD_SET(xcb_fd, &rfds);
- FD_SET(serv, &rfds);
- if (inotify_fd >= 0)
- FD_SET(inotify_fd, &rfds);
-
- int maxfd = xcb_fd;
- if (serv > maxfd)
- maxfd = serv;
- if (inotify_fd > maxfd)
- maxfd = inotify_fd;
-
- int r = select(maxfd + 1, &rfds, NULL, NULL, NULL);
- if (r < 0) {
- if (errno == EINTR)
- continue;
- warn("select: %s", strerror(errno));
- continue;
+ if (argc > 1 && !strcmp(argv[1], "focus")) {
+ conn = xcb_connect(NULL, NULL);
+ if (xcb_connection_has_error(conn)) {
+ fprintf(stderr, "chd: xcb_connect failed\n");
+ return 1;
+ }
+ root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
+ int ret = focus_first_window();
+ xcb_disconnect(conn);
+ return ret;
}
- if (inotify_fd >= 0 && FD_ISSET(inotify_fd, &rfds)) {
- char ibuf[sizeof(struct inotify_event) + NAME_MAX + 1];
- ssize_t nr;
- while ((nr = read(inotify_fd, ibuf, sizeof(ibuf))) > 0)
- (void)nr;
- reload_config(inotify_fd, &watch_wd);
+ if (argc > 1) {
+ fprintf(stderr, "chd: unknown argument '%s'\n", argv[1]);
+ return 1;
}
- if (FD_ISSET(serv, &rfds)) {
- int cli = accept(serv, NULL, NULL);
- if (cli < 0) {
- warn("accept: %s", strerror(errno));
- } else {
- if (fcntl(cli, F_SETFD, FD_CLOEXEC) < 0)
- warn("fcntl(CLOEXEC) on client fd: %s", strerror(errno));
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = handle_signal;
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGUSR1, &sa, NULL);
+ sigaction(SIGUSR2, &sa, NULL);
- char buf[MAX_CMD_LEN * 2];
- memset(buf, 0, sizeof(buf));
- ssize_t nr = read(cli, buf, sizeof(buf) - 1);
- if (nr > 0) {
- buf[nr] = '\0';
- int ok = add_binding(buf);
- const char *ack = (ok == 0) ? "ok\n" : "err\n";
- if (write(cli, ack, strlen(ack)) < 0)
- warn("write(client): %s", strerror(errno));
- } else if (nr < 0) {
- warn("read(client): %s", strerror(errno));
- }
- close(cli);
- }
- }
+ conn = xcb_connect(NULL, NULL);
+ if (xcb_connection_has_error(conn))
+ die("xcb_connect failed");
+
+ const xcb_setup_t *setup = xcb_get_setup(conn);
+ xcb_screen_t *screen = xcb_setup_roots_iterator(setup).data;
+ root = screen->root;
+ kbmap_min = setup->min_keycode;
+ kbmap_max = setup->max_keycode;
- if (FD_ISSET(xcb_fd, &rfds)) {
- if (xcb_connection_has_error(conn)) {
- warn("XCB connection error — shutting down");
- break;
- }
+ refresh_keymap();
+ get_cfg();
- xcb_generic_event_t *ev;
- while ((ev = xcb_poll_for_event(conn)) != NULL) {
- uint8_t type = ev->response_type & ~0x80;
+ int serv = make_socket();
+ int xfd = xcb_get_file_descriptor(conn);
- switch (type) {
- case XCB_KEY_PRESS: {
- xcb_key_press_event_t *kp = (xcb_key_press_event_t *)ev;
- uint16_t state = (uint16_t)(kp->state & CLEAN_MASK);
- for (int i = 0; i < bind_count; i++) {
- if (bindings[i].keycode == kp->detail && bindings[i].mod == state) {
- spawn(bindings[i].cmd);
- break;
- }
- }
- break;
+ while (!g_quit) {
+ if (xcb_connection_has_error(conn)) {
+ fprintf(stderr, "chd: lost xcb connection\n");
+ break;
}
- case XCB_MAPPING_NOTIFY: {
- xcb_mapping_notify_event_t *mn = (xcb_mapping_notify_event_t *)ev;
- if (mn->request == XCB_MAPPING_KEYBOARD) {
- fprintf(stderr, "piss: keyboard mapping changed, "
- "refreshing\n");
+
+ if (g_reload) {
+ g_reload = 0;
+ ungrab_all();
+ bind_count = 0;
refresh_keymap();
- }
- break;
}
- case 0: {
- xcb_generic_error_t *xe = (xcb_generic_error_t *)ev;
- warn("XCB error: code=%u major=%u minor=%u", xe->error_code,
- xe->major_code, xe->minor_code);
- break;
+
+ if (g_restart) {
+ ungrab_all();
+ xcb_disconnect(conn);
+ close(serv);
+ execvp(argv[0], argv);
+ die_errno("execvp");
}
- default:
- break;
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(xfd, &fds);
+ FD_SET(serv, &fds);
+ int nfds = (xfd > serv ? xfd : serv) + 1;
+
+ int r = select(nfds, &fds, NULL, NULL, NULL);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "chd: select: %s\n", strerror(errno));
+ break;
}
- free(ev);
- }
- }
- }
+ if (FD_ISSET(serv, &fds)) {
+ int c = accept(serv, NULL, NULL);
+ if (c < 0) {
+ fprintf(stderr, "chd: accept: %s\n", strerror(errno));
+ } else {
+ char buf[2048];
+ int n = read(c, buf, sizeof(buf) - 1);
+ if (n > 0) {
+ buf[n] = 0;
+ add_binding(buf);
+ } else if (n < 0) {
+ fprintf(stderr, "chd: read: %s\n", strerror(errno));
+ }
+ close(c);
+ }
+ }
+
+ if (FD_ISSET(xfd, &fds)) {
+ xcb_generic_event_t *ev;
+ while ((ev = xcb_poll_for_event(conn))) {
+ if ((ev->response_type & 0x7f) == XCB_KEY_PRESS) {
+ xcb_key_press_event_t *kp = (xcb_key_press_event_t *)ev;
+ uint16_t st = kp->state & CLEAN_MASK;
- ungrab_all();
- free(kbmap);
- xcb_disconnect(conn);
- unlink(SOCK_PATH);
- close(serv);
- if (inotify_fd >= 0)
- close(inotify_fd);
+ for (int i = 0; i < bind_count; i++) {
+ if (bindings[i].keycode == kp->detail &&
+ bindings[i].mod == st) {
+ spawn(bindings[i].cmd);
+ break;
+ }
+ }
+ }
+ free(ev);
+ }
+ }
+ }
- return 0;
+ ungrab_all();
+ xcb_disconnect(conn);
+ close(serv);
+ return 0;
}