sxhkd-rc

[fork] simple X hotkey daemon (but for the rc shell)
git clone https://hhvn.uk/sxhkd-rc
git clone git://hhvn.uk/sxhkd-rc
Log | Files | Refs | README | LICENSE

sxhkd.c (8986B)


      1 /* Copyright (c) 2013, Bastien Dejean
      2  * All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are met:
      6  *
      7  * 1. Redistributions of source code must retain the above copyright notice, this
      8  *    list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright notice,
     10  *    this list of conditions and the following disclaimer in the documentation
     11  *    and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     16  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
     17  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     20  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     23  */
     24 
     25 #include <xcb/xcb_event.h>
     26 #include <stdio.h>
     27 #include <stdlib.h>
     28 #include <unistd.h>
     29 #include <sys/stat.h>
     30 #include <sys/select.h>
     31 #include <sys/types.h>
     32 #include <sys/time.h>
     33 #include <fcntl.h>
     34 #include <signal.h>
     35 #include <stdbool.h>
     36 #include "parse.h"
     37 #include "grab.h"
     38 
     39 xcb_connection_t *dpy;
     40 xcb_window_t root;
     41 xcb_key_symbols_t *symbols;
     42 
     43 char *shell;
     44 char config_file[MAXLEN];
     45 char *config_path;
     46 char **extra_confs;
     47 int num_extra_confs;
     48 int redir_fd;
     49 FILE *status_fifo;
     50 char progress[3 * MAXLEN];
     51 int mapping_count;
     52 int timeout;
     53 
     54 hotkey_t *hotkeys_head, *hotkeys_tail;
     55 bool running, grabbed, toggle_grab, reload, bell, chained, locked;
     56 xcb_keysym_t abort_keysym;
     57 chord_t *abort_chord;
     58 
     59 uint16_t num_lock;
     60 uint16_t caps_lock;
     61 uint16_t scroll_lock;
     62 
     63 int main(int argc, char *argv[])
     64 {
     65 	int opt;
     66 	char *fifo_path = NULL;
     67 	status_fifo = NULL;
     68 	config_path = NULL;
     69 	mapping_count = 0;
     70 	timeout = TIMEOUT;
     71 	grabbed = false;
     72 	redir_fd = -1;
     73 	abort_keysym = ESCAPE_KEYSYM;
     74 
     75 	while ((opt = getopt(argc, argv, "hvm:t:c:r:s:a:")) != -1) {
     76 		switch (opt) {
     77 			case 'v':
     78 				printf("%s\n", VERSION);
     79 				exit(EXIT_SUCCESS);
     80 				break;
     81 			case 'h':
     82 				printf("sxhkd [-h|-v|-m COUNT|-t TIMEOUT|-c CONFIG_FILE|-r REDIR_FILE|-s STATUS_FIFO|-a ABORT_KEYSYM] [EXTRA_CONFIG ...]\n");
     83 				exit(EXIT_SUCCESS);
     84 				break;
     85 			case 'm':
     86 				if (sscanf(optarg, "%i", &mapping_count) != 1)
     87 					warn("Can't parse mapping count.\n");
     88 				break;
     89 			case 't':
     90 				timeout = atoi(optarg);
     91 				break;
     92 			case 'c':
     93 				config_path = optarg;
     94 				break;
     95 			case 'r':
     96 				redir_fd = open(optarg, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
     97 				if (redir_fd == -1)
     98 					warn("Failed to open the command redirection file.\n");
     99 				break;
    100 			case 's':
    101 				fifo_path = optarg;
    102 				break;
    103 			case 'a':
    104 				if (!parse_keysym(optarg, &abort_keysym)) {
    105 					warn("Invalid keysym name: %s.\n", optarg);
    106 				}
    107 				break;
    108 		}
    109 	}
    110 
    111 	num_extra_confs = argc - optind;
    112 	extra_confs = argv + optind;
    113 
    114 	if (config_path == NULL) {
    115 		char *config_home = getenv(CONFIG_HOME_ENV);
    116 		if (config_home != NULL)
    117 			snprintf(config_file, sizeof(config_file), "%s/%s", config_home, CONFIG_PATH);
    118 		else
    119 			snprintf(config_file, sizeof(config_file), "%s/%s/%s", getenv("HOME"), ".config", CONFIG_PATH);
    120 	} else {
    121 		snprintf(config_file, sizeof(config_file), "%s", config_path);
    122 	}
    123 
    124 	if (fifo_path != NULL) {
    125 		int fifo_fd = open(fifo_path, O_RDWR | O_NONBLOCK);
    126 		if (fifo_fd != -1) {
    127 			status_fifo = fdopen(fifo_fd, "w");
    128 		} else {
    129 			warn("Couldn't open status fifo.\n");
    130 		}
    131 	}
    132 
    133 	signal(SIGINT, hold);
    134 	signal(SIGHUP, hold);
    135 	signal(SIGTERM, hold);
    136 	signal(SIGUSR1, hold);
    137 	signal(SIGUSR2, hold);
    138 	signal(SIGALRM, hold);
    139 
    140 	setup();
    141 	get_standard_keysyms();
    142 	get_lock_fields();
    143 	abort_chord = make_chord(abort_keysym, XCB_NONE, 0, XCB_KEY_PRESS, false, false);
    144 	load_config(config_file);
    145 	for (int i = 0; i < num_extra_confs; i++)
    146 		load_config(extra_confs[i]);
    147 	grab();
    148 
    149 	xcb_generic_event_t *evt;
    150 	int fd = xcb_get_file_descriptor(dpy);
    151 
    152 	fd_set descriptors;
    153 
    154 	reload = toggle_grab = bell = chained = locked = false;
    155 	running = true;
    156 
    157 	xcb_flush(dpy);
    158 
    159 	while (running) {
    160 		FD_ZERO(&descriptors);
    161 		FD_SET(fd, &descriptors);
    162 
    163 		if (select(fd + 1, &descriptors, NULL, NULL, NULL) > 0) {
    164 			while ((evt = xcb_poll_for_event(dpy)) != NULL) {
    165 				uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(evt);
    166 				switch (event_type) {
    167 					case XCB_KEY_PRESS:
    168 					case XCB_KEY_RELEASE:
    169 					case XCB_BUTTON_PRESS:
    170 					case XCB_BUTTON_RELEASE:
    171 						key_button_event(evt, event_type);
    172 						break;
    173 					case XCB_MAPPING_NOTIFY:
    174 						mapping_notify(evt);
    175 						break;
    176 					default:
    177 						PRINTF("received event %u\n", event_type);
    178 						break;
    179 				}
    180 				free(evt);
    181 			}
    182 		}
    183 
    184 		if (reload) {
    185 			signal(SIGUSR1, hold);
    186 			reload_cmd();
    187 			reload = false;
    188 		}
    189 
    190 		if (toggle_grab) {
    191 			signal(SIGUSR2, hold);
    192 			toggle_grab_cmd();
    193 			toggle_grab = false;
    194 		}
    195 
    196 		if (bell) {
    197 			signal(SIGALRM, hold);
    198 			put_status(TIMEOUT_PREFIX, "Timeout reached");
    199 			abort_chain();
    200 			bell = false;
    201 		}
    202 
    203 		if (xcb_connection_has_error(dpy)) {
    204 			warn("The server closed the connection.\n");
    205 			running = false;
    206 		}
    207 	}
    208 
    209 	if (redir_fd != -1) {
    210 		close(redir_fd);
    211 	}
    212 
    213 	if (status_fifo != NULL) {
    214 		fclose(status_fifo);
    215 	}
    216 
    217 	ungrab();
    218 	cleanup();
    219 	destroy_chord(abort_chord);
    220 	xcb_key_symbols_free(symbols);
    221 	xcb_disconnect(dpy);
    222 	return EXIT_SUCCESS;
    223 }
    224 
    225 void key_button_event(xcb_generic_event_t *evt, uint8_t event_type)
    226 {
    227 	xcb_keysym_t keysym = XCB_NO_SYMBOL;
    228 	xcb_button_t button = XCB_NONE;
    229 	bool replay_event = false;
    230 	uint16_t modfield = 0;
    231 	uint16_t lockfield = num_lock | caps_lock | scroll_lock;
    232 	parse_event(evt, event_type, &keysym, &button, &modfield);
    233 	modfield &= ~lockfield & MOD_STATE_FIELD;
    234 	if (keysym != XCB_NO_SYMBOL || button != XCB_NONE) {
    235 		hotkey_t *hk = find_hotkey(keysym, button, modfield, event_type, &replay_event);
    236 		if (hk != NULL) {
    237 			run(hk->command, hk->sync);
    238 			put_status(COMMAND_PREFIX, hk->command);
    239 		}
    240 	}
    241 	switch (event_type) {
    242 		case XCB_BUTTON_PRESS:
    243 		case XCB_BUTTON_RELEASE:
    244 			if (replay_event)
    245 				xcb_allow_events(dpy, XCB_ALLOW_REPLAY_POINTER, XCB_CURRENT_TIME);
    246 			else
    247 				xcb_allow_events(dpy, XCB_ALLOW_SYNC_POINTER, XCB_CURRENT_TIME);
    248 			break;
    249 		case XCB_KEY_PRESS:
    250 		case XCB_KEY_RELEASE:
    251 			if (replay_event)
    252 				xcb_allow_events(dpy, XCB_ALLOW_REPLAY_KEYBOARD, XCB_CURRENT_TIME);
    253 			else
    254 				xcb_allow_events(dpy, XCB_ALLOW_SYNC_KEYBOARD, XCB_CURRENT_TIME);
    255 			break;
    256 	}
    257 	xcb_flush(dpy);
    258 }
    259 
    260 void mapping_notify(xcb_generic_event_t *evt)
    261 {
    262 	if (!mapping_count)
    263 		return;
    264 	xcb_mapping_notify_event_t *e = (xcb_mapping_notify_event_t *) evt;
    265 	PRINTF("mapping notify %u %u\n", e->request, e->count);
    266 	if (e->request == XCB_MAPPING_POINTER)
    267 		return;
    268 	if (xcb_refresh_keyboard_mapping(symbols, e) == 1) {
    269 		destroy_chord(abort_chord);
    270 		get_lock_fields();
    271 		reload_cmd();
    272 		abort_chord = make_chord(abort_keysym, XCB_NONE, 0, XCB_KEY_PRESS, false, false);
    273 		if (mapping_count > 0)
    274 			mapping_count--;
    275 	}
    276 }
    277 
    278 void setup(void)
    279 {
    280 	int screen_idx;
    281 	dpy = xcb_connect(NULL, &screen_idx);
    282 	if (xcb_connection_has_error(dpy))
    283 		err("Can't open display.\n");
    284 	xcb_screen_t *screen = NULL;
    285 	xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(xcb_get_setup(dpy));
    286 	for (; screen_iter.rem; xcb_screen_next(&screen_iter), screen_idx--) {
    287 		if (screen_idx == 0) {
    288 			screen = screen_iter.data;
    289 			break;
    290 		}
    291 	}
    292 	if (screen == NULL)
    293 		err("Can't acquire screen.\n");
    294 	root = screen->root;
    295 	if ((shell = getenv(SXHKD_SHELL_ENV)) == NULL && (shell = getenv(SHELL_ENV)) == NULL)
    296 		err("The '%s' environment variable is not defined.\n", SHELL_ENV);
    297 	symbols = xcb_key_symbols_alloc(dpy);
    298 	hotkeys_head = hotkeys_tail = NULL;
    299 	progress[0] = '\0';
    300 }
    301 
    302 void cleanup(void)
    303 {
    304 	PUTS("cleanup");
    305 	hotkey_t *hk = hotkeys_head;
    306 	while (hk != NULL) {
    307 		hotkey_t *next = hk->next;
    308 		destroy_chain(hk->chain);
    309 		free(hk->cycle);
    310 		free(hk);
    311 		hk = next;
    312 	}
    313 	hotkeys_head = hotkeys_tail = NULL;
    314 }
    315 
    316 void reload_cmd(void)
    317 {
    318 	PUTS("reload");
    319 	cleanup();
    320 	load_config(config_file);
    321 	for (int i = 0; i < num_extra_confs; i++)
    322 		load_config(extra_confs[i]);
    323 	ungrab();
    324 	grab();
    325 }
    326 
    327 void toggle_grab_cmd(void)
    328 {
    329 	PUTS("toggle grab");
    330 	if (grabbed) {
    331 		ungrab();
    332 	} else {
    333 		grab();
    334 	}
    335 }
    336 
    337 void hold(int sig)
    338 {
    339 	if (sig == SIGHUP || sig == SIGINT || sig == SIGTERM)
    340 		running = false;
    341 	else if (sig == SIGUSR1)
    342 		reload = true;
    343 	else if (sig == SIGUSR2)
    344 		toggle_grab = true;
    345 	else if (sig == SIGALRM)
    346 		bell = true;
    347 }
    348 
    349 void put_status(char c, const char *s)
    350 {
    351 	if (status_fifo == NULL) {
    352 		return;
    353 	}
    354 	fprintf(status_fifo, "%c%s\n", c, s);
    355 	fflush(status_fifo);
    356 }