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 }