You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

2101 lines
67 KiB

  1. #include "config.h"
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <stdbool.h>
  6. #include <ctype.h>
  7. #include <unistd.h>
  8. #include <assert.h>
  9. #include <errno.h>
  10. #include <pwd.h>
  11. #include <fcntl.h>
  12. #include <sys/types.h>
  13. #include <sys/stat.h>
  14. #include <linux/input-event-codes.h>
  15. #include <xkbcommon/xkbcommon.h>
  16. #include <fontconfig/fontconfig.h>
  17. #define LOG_MODULE "config"
  18. #define LOG_ENABLE_DBG 0
  19. #include "log.h"
  20. #include "input.h"
  21. #include "macros.h"
  22. #include "tokenize.h"
  23. #include "util.h"
  24. #include "wayland.h"
  25. #include "xmalloc.h"
  26. static const uint32_t default_foreground = 0xdcdccc;
  27. static const uint32_t default_background = 0x111111;
  28. static const uint32_t default_regular[] = {
  29. 0x222222,
  30. 0xcc9393,
  31. 0x7f9f7f,
  32. 0xd0bf8f,
  33. 0x6ca0a3,
  34. 0xdc8cc3,
  35. 0x93e0e3,
  36. 0xdcdccc,
  37. };
  38. static const uint32_t default_bright[] = {
  39. 0x666666,
  40. 0xdca3a3,
  41. 0xbfebbf,
  42. 0xf0dfaf,
  43. 0x8cd0d3,
  44. 0xfcace3,
  45. 0xb3ffff,
  46. 0xffffff,
  47. };
  48. static const char *const binding_action_map[] = {
  49. [BIND_ACTION_NONE] = NULL,
  50. [BIND_ACTION_SCROLLBACK_UP] = "scrollback-up",
  51. [BIND_ACTION_SCROLLBACK_UP_PAGE] = "scrollback-up-page",
  52. [BIND_ACTION_SCROLLBACK_UP_HALF_PAGE] = "scrollback-up-half-page",
  53. [BIND_ACTION_SCROLLBACK_UP_LINE] = "scrollback-up-line",
  54. [BIND_ACTION_SCROLLBACK_DOWN] = "scrollback-down",
  55. [BIND_ACTION_SCROLLBACK_DOWN_PAGE] = "scrollback-down-page",
  56. [BIND_ACTION_SCROLLBACK_DOWN_HALF_PAGE] = "scrollback-down-half-page",
  57. [BIND_ACTION_SCROLLBACK_DOWN_LINE] = "scrollback-down-line",
  58. [BIND_ACTION_CLIPBOARD_COPY] = "clipboard-copy",
  59. [BIND_ACTION_CLIPBOARD_PASTE] = "clipboard-paste",
  60. [BIND_ACTION_PRIMARY_PASTE] = "primary-paste",
  61. [BIND_ACTION_SEARCH_START] = "search-start",
  62. [BIND_ACTION_FONT_SIZE_UP] = "font-increase",
  63. [BIND_ACTION_FONT_SIZE_DOWN] = "font-decrease",
  64. [BIND_ACTION_FONT_SIZE_RESET] = "font-reset",
  65. [BIND_ACTION_SPAWN_TERMINAL] = "spawn-terminal",
  66. [BIND_ACTION_MINIMIZE] = "minimize",
  67. [BIND_ACTION_MAXIMIZE] = "maximize",
  68. [BIND_ACTION_FULLSCREEN] = "fullscreen",
  69. [BIND_ACTION_PIPE_SCROLLBACK] = "pipe-scrollback",
  70. [BIND_ACTION_PIPE_VIEW] = "pipe-visible",
  71. [BIND_ACTION_PIPE_SELECTED] = "pipe-selected",
  72. /* Mouse-specific actions */
  73. [BIND_ACTION_SELECT_BEGIN] = "select-begin",
  74. [BIND_ACTION_SELECT_BEGIN_BLOCK] = "select-begin-block",
  75. [BIND_ACTION_SELECT_EXTEND] = "select-extend",
  76. [BIND_ACTION_SELECT_WORD] = "select-word",
  77. [BIND_ACTION_SELECT_WORD_WS] = "select-word-whitespace",
  78. [BIND_ACTION_SELECT_ROW] = "select-row",
  79. };
  80. static_assert(ALEN(binding_action_map) == BIND_ACTION_COUNT,
  81. "binding action map size mismatch");
  82. static const char *const search_binding_action_map[] = {
  83. [BIND_ACTION_SEARCH_NONE] = NULL,
  84. [BIND_ACTION_SEARCH_CANCEL] = "cancel",
  85. [BIND_ACTION_SEARCH_COMMIT] = "commit",
  86. [BIND_ACTION_SEARCH_FIND_PREV] = "find-prev",
  87. [BIND_ACTION_SEARCH_FIND_NEXT] = "find-next",
  88. [BIND_ACTION_SEARCH_EDIT_LEFT] = "cursor-left",
  89. [BIND_ACTION_SEARCH_EDIT_LEFT_WORD] = "cursor-left-word",
  90. [BIND_ACTION_SEARCH_EDIT_RIGHT] = "cursor-right",
  91. [BIND_ACTION_SEARCH_EDIT_RIGHT_WORD] = "cursor-right-word",
  92. [BIND_ACTION_SEARCH_EDIT_HOME] = "cursor-home",
  93. [BIND_ACTION_SEARCH_EDIT_END] = "cursor-end",
  94. [BIND_ACTION_SEARCH_DELETE_PREV] = "delete-prev",
  95. [BIND_ACTION_SEARCH_DELETE_PREV_WORD] = "delete-prev-word",
  96. [BIND_ACTION_SEARCH_DELETE_NEXT] = "delete-next",
  97. [BIND_ACTION_SEARCH_DELETE_NEXT_WORD] = "delete-next-word",
  98. [BIND_ACTION_SEARCH_EXTEND_WORD] = "extend-to-word-boundary",
  99. [BIND_ACTION_SEARCH_EXTEND_WORD_WS] = "extend-to-next-whitespace",
  100. };
  101. static_assert(ALEN(search_binding_action_map) == BIND_ACTION_SEARCH_COUNT,
  102. "search binding action map size mismatch");
  103. #define LOG_AND_NOTIFY_ERR(...) \
  104. do { \
  105. LOG_ERR(__VA_ARGS__); \
  106. char *text = xasprintf(__VA_ARGS__); \
  107. struct user_notification notif = { \
  108. .kind = USER_NOTIFICATION_ERROR, \
  109. .text = text, \
  110. }; \
  111. tll_push_back(conf->notifications, notif); \
  112. } while (0)
  113. #define LOG_AND_NOTIFY_WARN(...) \
  114. do { \
  115. LOG_WARN(__VA_ARGS__); \
  116. char *text = xasprintf(__VA_ARGS__); \
  117. struct user_notification notif = { \
  118. .kind = USER_NOTIFICATION_WARNING, \
  119. .text = text, \
  120. }; \
  121. tll_push_back(conf->notifications, notif); \
  122. } while (0)
  123. #define LOG_AND_NOTIFY_ERRNO(...) \
  124. do { \
  125. int _errno = errno; \
  126. LOG_ERRNO(__VA_ARGS__); \
  127. int len = snprintf(NULL, 0, __VA_ARGS__); \
  128. int errno_len = snprintf(NULL, 0, ": %s", strerror(_errno)); \
  129. char *text = xmalloc(len + errno_len + 1); \
  130. snprintf(text, len + errno_len + 1, __VA_ARGS__); \
  131. snprintf(&text[len], errno_len + 1, ": %s", strerror(_errno)); \
  132. struct user_notification notif = { \
  133. .kind = USER_NOTIFICATION_ERROR, \
  134. .text = text, \
  135. }; \
  136. tll_push_back(conf->notifications, notif); \
  137. } while(0)
  138. static char *
  139. get_shell(void)
  140. {
  141. const char *shell = getenv("SHELL");
  142. if (shell == NULL) {
  143. struct passwd *passwd = getpwuid(getuid());
  144. if (passwd == NULL) {
  145. LOG_ERRNO("failed to lookup user: falling back to 'sh'");
  146. shell = "sh";
  147. } else
  148. shell = passwd->pw_shell;
  149. }
  150. LOG_DBG("user's shell: %s", shell);
  151. return xstrdup(shell);
  152. }
  153. struct config_file {
  154. char *path; /* Full, absolute, path */
  155. int fd; /* FD of file, O_RDONLY */
  156. };
  157. struct path_component {
  158. const char *component;
  159. int fd;
  160. };
  161. typedef tll(struct path_component) path_components_t;
  162. static void
  163. path_component_add(path_components_t *components, const char *comp, int fd)
  164. {
  165. assert(comp != NULL);
  166. assert(fd >= 0);
  167. struct path_component pc = {.component = comp, .fd = fd};
  168. tll_push_back(*components, pc);
  169. }
  170. static void
  171. path_component_destroy(struct path_component *component)
  172. {
  173. assert(component->fd >= 0);
  174. close(component->fd);
  175. }
  176. static void
  177. path_components_destroy(path_components_t *components)
  178. {
  179. tll_foreach(*components, it) {
  180. path_component_destroy(&it->item);
  181. tll_remove(*components, it);
  182. }
  183. }
  184. static struct config_file
  185. path_components_to_config_file(const path_components_t *components)
  186. {
  187. if (tll_length(*components) == 0)
  188. goto err;
  189. size_t len = 0;
  190. tll_foreach(*components, it)
  191. len += strlen(it->item.component) + 1;
  192. char *path = malloc(len);
  193. if (path == NULL)
  194. goto err;
  195. size_t idx = 0;
  196. tll_foreach(*components, it) {
  197. strcpy(&path[idx], it->item.component);
  198. idx += strlen(it->item.component);
  199. path[idx++] = '/';
  200. }
  201. path[idx - 1] = '\0'; /* Strip last ’/’ */
  202. int fd_copy = dup(tll_back(*components).fd);
  203. if (fd_copy < 0) {
  204. free(path);
  205. goto err;
  206. }
  207. return (struct config_file){.path = path, .fd = fd_copy};
  208. err:
  209. return (struct config_file){.path = NULL, .fd = -1};
  210. }
  211. static const char *
  212. get_user_home_dir(void)
  213. {
  214. const struct passwd *passwd = getpwuid(getuid());
  215. if (passwd == NULL)
  216. return NULL;
  217. return passwd->pw_dir;
  218. }
  219. static bool
  220. try_open_file(path_components_t *components, const char *name)
  221. {
  222. int parent_fd = tll_back(*components).fd;
  223. struct stat st;
  224. if (fstatat(parent_fd, name, &st, 0) == 0 && S_ISREG(st.st_mode)) {
  225. int fd = openat(parent_fd, name, O_RDONLY);
  226. if (fd >= 0) {
  227. path_component_add(components, name, fd);
  228. return true;
  229. }
  230. }
  231. return false;
  232. }
  233. static struct config_file
  234. open_config(struct config *conf)
  235. {
  236. struct config_file ret = {.path = NULL, .fd = -1};
  237. bool log_deprecation = false;
  238. path_components_t components = tll_init();
  239. const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
  240. const char *user_home_dir = get_user_home_dir();
  241. char *xdg_config_dirs_copy = NULL;
  242. /* Use XDG_CONFIG_HOME, or ~/.config */
  243. if (xdg_config_home != NULL) {
  244. int fd = open(xdg_config_home, O_RDONLY);
  245. if (fd >= 0)
  246. path_component_add(&components, xdg_config_home, fd);
  247. } else if (user_home_dir != NULL) {
  248. int home_fd = open(user_home_dir, O_RDONLY);
  249. if (home_fd >= 0) {
  250. int config_fd = openat(home_fd, ".config", O_RDONLY);
  251. if (config_fd >= 0) {
  252. path_component_add(&components, user_home_dir, home_fd);
  253. path_component_add(&components, ".config", config_fd);
  254. } else
  255. close(home_fd);
  256. }
  257. }
  258. /* First look for foot/foot.ini */
  259. if (tll_length(components) > 0) {
  260. int foot_fd = openat(tll_back(components).fd, "foot", O_RDONLY);
  261. if (foot_fd >= 0) {
  262. path_component_add(&components, "foot", foot_fd);
  263. if (try_open_file(&components, "foot.ini"))
  264. goto done;
  265. struct path_component pc = tll_pop_back(components);
  266. path_component_destroy(&pc);
  267. }
  268. }
  269. /* Next try footrc */
  270. if (tll_length(components) > 0 && try_open_file(&components, "footrc")) {
  271. log_deprecation = true;
  272. goto done;
  273. }
  274. /* Finally, try foot/foot.ini in all XDG_CONFIG_DIRS */
  275. const char *xdg_config_dirs = getenv("XDG_CONFIG_DIRS");
  276. xdg_config_dirs_copy = xdg_config_dirs != NULL
  277. ? strdup(xdg_config_dirs) : NULL;
  278. if (xdg_config_dirs_copy != NULL) {
  279. for (char *save = NULL,
  280. *xdg_dir = strtok_r(xdg_config_dirs_copy, ":", &save);
  281. xdg_dir != NULL;
  282. xdg_dir = strtok_r(NULL, ":", &save))
  283. {
  284. path_components_destroy(&components);
  285. int xdg_fd = open(xdg_dir, O_RDONLY);
  286. if (xdg_fd < 0)
  287. continue;
  288. int foot_fd = openat(xdg_fd, "foot", O_RDONLY);
  289. if (foot_fd < 0) {
  290. close(xdg_fd);
  291. continue;
  292. }
  293. assert(tll_length(components) == 0);
  294. path_component_add(&components, xdg_dir, xdg_fd);
  295. path_component_add(&components, "foot", foot_fd);
  296. if (try_open_file(&components, "foot.ini"))
  297. goto done;
  298. }
  299. }
  300. out:
  301. path_components_destroy(&components);
  302. free(xdg_config_dirs_copy);
  303. return ret;
  304. done:
  305. assert(tll_length(components) > 0);
  306. ret = path_components_to_config_file(&components);
  307. if (log_deprecation && ret.path != NULL) {
  308. LOG_WARN("deprecated: configuration in $XDG_CONFIG_HOME/footrc, "
  309. "use $XDG_CONFIG_HOME/foot/foot.ini instead");
  310. char *text = xstrdup(
  311. "configuration in \033[31m$XDG_CONFIG_HOME/footrc\033[39m or "
  312. "\033[31m~/.config/footrc\033[39m, "
  313. "use \033[32m$XDG_CONFIG_HOME/foot/foot.ini\033[39m or "
  314. "\033[32m~/.config/foot/foot.ini\033[39m instead");
  315. struct user_notification deprecation = {
  316. .kind = USER_NOTIFICATION_DEPRECATED,
  317. .text = text,
  318. };
  319. tll_push_back(conf->notifications, deprecation);
  320. }
  321. goto out;
  322. }
  323. static bool
  324. str_to_bool(const char *s)
  325. {
  326. return strcasecmp(s, "on") == 0 ||
  327. strcasecmp(s, "true") == 0 ||
  328. strcasecmp(s, "yes") == 0 ||
  329. strtoul(s, NULL, 0) > 0;
  330. }
  331. static bool
  332. str_to_ulong(const char *s, int base, unsigned long *res)
  333. {
  334. if (s == NULL)
  335. return false;
  336. errno = 0;
  337. char *end = NULL;
  338. *res = strtoul(s, &end, base);
  339. return errno == 0 && *end == '\0';
  340. }
  341. static bool
  342. str_to_double(const char *s, double *res)
  343. {
  344. if (s == NULL)
  345. return false;
  346. errno = 0;
  347. char *end = NULL;
  348. *res = strtod(s, &end);
  349. return errno == 0 && *end == '\0';
  350. }
  351. static bool
  352. str_to_color(const char *s, uint32_t *color, bool allow_alpha, const char *path, int lineno,
  353. const char *section, const char *key)
  354. {
  355. unsigned long value;
  356. if (!str_to_ulong(s, 16, &value)) {
  357. LOG_ERRNO("%s:%d: [%s]: %s: invalid color: %s", path, lineno, section, key, s);
  358. return false;
  359. }
  360. if (!allow_alpha && (value & 0xff000000) != 0) {
  361. LOG_ERR("%s:%d: [%s]: %s: color value must not have an alpha component: %s",
  362. path, lineno, section, key, s);
  363. return false;
  364. }
  365. *color = value;
  366. return true;
  367. }
  368. static bool
  369. parse_section_main(const char *key, const char *value, struct config *conf,
  370. const char *path, unsigned lineno)
  371. {
  372. if (strcmp(key, "term") == 0) {
  373. free(conf->term);
  374. conf->term = xstrdup(value);
  375. }
  376. else if (strcmp(key, "shell") == 0) {
  377. free(conf->shell);
  378. conf->shell = xstrdup(value);
  379. }
  380. else if (strcmp(key, "login-shell") == 0) {
  381. conf->login_shell = str_to_bool(value);
  382. }
  383. else if (strcmp(key, "title") == 0) {
  384. free(conf->title);
  385. conf->title = xstrdup(value);
  386. }
  387. else if (strcmp(key, "app-id") == 0) {
  388. free(conf->app_id);
  389. conf->app_id = xstrdup(value);
  390. }
  391. else if (strcmp(key, "initial-window-size-pixels") == 0 ||
  392. strcmp(key, "geometry") == 0 /* deprecated */)
  393. {
  394. if (strcmp(key, "geometry") == 0) {
  395. LOG_WARN("deprecated: %s:%d: [default]: geometry: use 'initial-window-size-pixels' instead'", path, lineno);
  396. const char fmt[] = "%s:%d: \033[1mgeometry\033[21m, use \033[1minitial-window-size-pixels\033[21m instead";
  397. char *text = xasprintf(fmt, path, lineno);
  398. struct user_notification deprecation = {
  399. .kind = USER_NOTIFICATION_DEPRECATED,
  400. .text = text,
  401. };
  402. tll_push_back(conf->notifications, deprecation);
  403. }
  404. unsigned width, height;
  405. if (sscanf(value, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
  406. LOG_AND_NOTIFY_ERR(
  407. "%s: %d: [default]: initial-window-size-pixels: "
  408. "expected WIDTHxHEIGHT, where both are positive integers, "
  409. "got '%s'", path, lineno, value);
  410. return false;
  411. }
  412. conf->size.type = CONF_SIZE_PX;
  413. conf->size.px.width = width;
  414. conf->size.px.height = height;
  415. }
  416. else if (strcmp(key, "initial-window-size-chars") == 0) {
  417. unsigned width, height;
  418. if (sscanf(value, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
  419. LOG_AND_NOTIFY_ERR(
  420. "%s: %d: [default]: initial-window-size-chars: "
  421. "expected WIDTHxHEIGHT, where both are positive integers, "
  422. "got '%s'", path, lineno, value);
  423. return false;
  424. }
  425. conf->size.type = CONF_SIZE_CELLS;
  426. conf->size.cells.width = width;
  427. conf->size.cells.height = height;
  428. }
  429. else if (strcmp(key, "pad") == 0) {
  430. unsigned x, y;
  431. if (sscanf(value, "%ux%u", &x, &y) != 2) {
  432. LOG_AND_NOTIFY_ERR(
  433. "%s:%d: [default]: pad: expected PAD_XxPAD_Y, "
  434. "where both are positive integers, got '%s'",
  435. path, lineno, value);
  436. return false;
  437. }
  438. conf->pad_x = x;
  439. conf->pad_y = y;
  440. }
  441. else if (strcmp(key, "bell") == 0) {
  442. if (strcmp(value, "set-urgency") == 0)
  443. conf->bell_is_urgent = true;
  444. else if (strcmp(value, "none") == 0)
  445. conf->bell_is_urgent = false;
  446. else {
  447. LOG_AND_NOTIFY_ERR(
  448. "%s:%d: [default]: bell: "
  449. "expected either 'set-urgency' or 'none'", path, lineno);
  450. conf->bell_is_urgent = false;
  451. return false;
  452. }
  453. }
  454. else if (strcmp(key, "initial-window-mode") == 0) {
  455. if (strcmp(value, "windowed") == 0)
  456. conf->startup_mode = STARTUP_WINDOWED;
  457. else if (strcmp(value, "maximized") == 0)
  458. conf->startup_mode = STARTUP_MAXIMIZED;
  459. else if (strcmp(value, "fullscreen") == 0)
  460. conf->startup_mode = STARTUP_FULLSCREEN;
  461. else {
  462. LOG_AND_NOTIFY_ERR(
  463. "%s:%d: [default]: initial-window-mode: expected either "
  464. "'windowed', 'maximized' or 'fullscreen'",
  465. path, lineno);
  466. return false;
  467. }
  468. }
  469. else if (strcmp(key, "font") == 0) {
  470. char *copy = xstrdup(value);
  471. for (const char *font = strtok(copy, ","); font != NULL; font = strtok(NULL, ",")) {
  472. /* Trim spaces, strictly speaking not necessary, but looks nice :) */
  473. while (*font != '\0' && isspace(*font))
  474. font++;
  475. if (*font != '\0')
  476. tll_push_back(conf->fonts, config_font_parse(font));
  477. }
  478. free(copy);
  479. }
  480. else if (strcmp(key, "workers") == 0) {
  481. unsigned long count;
  482. if (!str_to_ulong(value, 10, &count)) {
  483. LOG_AND_NOTIFY_ERR(
  484. "%s:%d: [default]: workers: expected an integer, got '%s'",
  485. path, lineno, value);
  486. return false;
  487. }
  488. conf->render_worker_count = count;
  489. }
  490. else if (strcmp(key, "word-delimiters") == 0) {
  491. size_t chars = mbstowcs(NULL, value, 0);
  492. if (chars == (size_t)-1) {
  493. LOG_AND_NOTIFY_ERR(
  494. "%s:%d: [default]: word-delimiters: invalid string: %s",
  495. path, lineno, value);
  496. return false;
  497. }
  498. free(conf->word_delimiters);
  499. conf->word_delimiters = xmalloc((chars + 1) * sizeof(wchar_t));
  500. mbstowcs(conf->word_delimiters, value, chars + 1);
  501. }
  502. else if (strcmp(key, "scrollback") == 0) {
  503. LOG_WARN("deprecated: %s:%d: [default]: scrollback: use 'scrollback.lines' instead'", path, lineno);
  504. const char fmt[] = "%s:%d: \033[1mdefault.scrollback\033[21m, use \033[1mscrollback.lines\033[21m instead";
  505. char *text = xasprintf(fmt, path, lineno);
  506. struct user_notification deprecation = {
  507. .kind = USER_NOTIFICATION_DEPRECATED,
  508. .text = text,
  509. };
  510. tll_push_back(conf->notifications, deprecation);
  511. unsigned long lines;
  512. if (!str_to_ulong(value, 10, &lines)) {
  513. LOG_AND_NOTIFY_ERR(
  514. "%s:%d: [default]: scrollback: expected an integer, got '%s'",
  515. path, lineno, value);
  516. return false;
  517. }
  518. conf->scrollback.lines = lines;
  519. }
  520. else {
  521. LOG_AND_NOTIFY_ERR("%s:%u: [default]: %s: invalid key", path, lineno, key);
  522. return false;
  523. }
  524. return true;
  525. }
  526. static bool
  527. parse_section_scrollback(const char *key, const char *value, struct config *conf,
  528. const char *path, unsigned lineno)
  529. {
  530. if (strcmp(key, "lines") == 0) {
  531. unsigned long lines;
  532. if (!str_to_ulong(value, 10, &lines)) {
  533. LOG_AND_NOTIFY_ERR("%s:%d: [scrollback]: lines: expected an integer, got '%s'", path, lineno, value);
  534. return false;
  535. }
  536. conf->scrollback.lines = lines;
  537. }
  538. else if (strcmp(key, "indicator-position") == 0) {
  539. if (strcmp(value, "none") == 0)
  540. conf->scrollback.indicator.position = SCROLLBACK_INDICATOR_POSITION_NONE;
  541. else if (strcmp(value, "fixed") == 0)
  542. conf->scrollback.indicator.position = SCROLLBACK_INDICATOR_POSITION_FIXED;
  543. else if (strcmp(value, "relative") == 0)
  544. conf->scrollback.indicator.position = SCROLLBACK_INDICATOR_POSITION_RELATIVE;
  545. else {
  546. LOG_AND_NOTIFY_ERR("%s:%d: [scrollback]: indicator-position must be one of "
  547. "'none', 'fixed' or 'relative'",
  548. path, lineno);
  549. return false;
  550. }
  551. }
  552. else if (strcmp(key, "indicator-format") == 0) {
  553. if (strcmp(value, "percentage") == 0) {
  554. conf->scrollback.indicator.format
  555. = SCROLLBACK_INDICATOR_FORMAT_PERCENTAGE;
  556. } else if (strcmp(value, "line") == 0) {
  557. conf->scrollback.indicator.format
  558. = SCROLLBACK_INDICATOR_FORMAT_LINENO;
  559. } else {
  560. free(conf->scrollback.indicator.text);
  561. conf->scrollback.indicator.text = NULL;
  562. size_t len = mbstowcs(NULL, value, 0);
  563. if (len == (size_t)-1) {
  564. LOG_AND_NOTIFY_ERRNO(
  565. "%s:%d: [scrollback]: indicator-format: "
  566. "invalid value: %s", path, lineno, value);
  567. return false;
  568. }
  569. conf->scrollback.indicator.text = xcalloc(len + 1, sizeof(wchar_t));
  570. mbstowcs(conf->scrollback.indicator.text, value, len + 1);
  571. }
  572. }
  573. else if (strcmp(key, "multiplier") == 0) {
  574. double multiplier;
  575. if (!str_to_double(value, &multiplier)) {
  576. LOG_AND_NOTIFY_ERR("%s:%d: [scrollback]: multiplier: "
  577. "invalid value: %s", path, lineno, value);
  578. return false;
  579. }
  580. conf->scrollback.multiplier = multiplier;
  581. }
  582. else {
  583. LOG_AND_NOTIFY_ERR("%s:%u: [scrollback]: %s: invalid key", path, lineno, key);
  584. return false;
  585. }
  586. return true;
  587. }
  588. static bool
  589. parse_section_colors(const char *key, const char *value, struct config *conf,
  590. const char *path, unsigned lineno)
  591. {
  592. uint32_t *color = NULL;
  593. if (strcmp(key, "foreground") == 0) color = &conf->colors.fg;
  594. else if (strcmp(key, "background") == 0) color = &conf->colors.bg;
  595. else if (strcmp(key, "regular0") == 0) color = &conf->colors.regular[0];
  596. else if (strcmp(key, "regular1") == 0) color = &conf->colors.regular[1];
  597. else if (strcmp(key, "regular2") == 0) color = &conf->colors.regular[2];
  598. else if (strcmp(key, "regular3") == 0) color = &conf->colors.regular[3];
  599. else if (strcmp(key, "regular4") == 0) color = &conf->colors.regular[4];
  600. else if (strcmp(key, "regular5") == 0) color = &conf->colors.regular[5];
  601. else if (strcmp(key, "regular6") == 0) color = &conf->colors.regular[6];
  602. else if (strcmp(key, "regular7") == 0) color = &conf->colors.regular[7];
  603. else if (strcmp(key, "bright0") == 0) color = &conf->colors.bright[0];
  604. else if (strcmp(key, "bright1") == 0) color = &conf->colors.bright[1];
  605. else if (strcmp(key, "bright2") == 0) color = &conf->colors.bright[2];
  606. else if (strcmp(key, "bright3") == 0) color = &conf->colors.bright[3];
  607. else if (strcmp(key, "bright4") == 0) color = &conf->colors.bright[4];
  608. else if (strcmp(key, "bright5") == 0) color = &conf->colors.bright[5];
  609. else if (strcmp(key, "bright6") == 0) color = &conf->colors.bright[6];
  610. else if (strcmp(key, "bright7") == 0) color = &conf->colors.bright[7];
  611. else if (strcmp(key, "selection-foreground") == 0) color = &conf->colors.selection_fg;
  612. else if (strcmp(key, "selection-background") == 0) color = &conf->colors.selection_bg;
  613. else if (strcmp(key, "alpha") == 0) {
  614. double alpha;
  615. if (!str_to_double(value, &alpha) || alpha < 0. || alpha > 1.) {
  616. LOG_AND_NOTIFY_ERR("%s: %d: [colors]: alpha: expected a value in the range 0.0-1.0",
  617. path, lineno);
  618. return false;
  619. }
  620. conf->colors.alpha = alpha * 65535.;
  621. return true;
  622. }
  623. else {
  624. LOG_AND_NOTIFY_ERR("%s:%d: [colors]: %s: invalid key", path, lineno, key);
  625. return false;
  626. }
  627. uint32_t color_value;
  628. if (!str_to_color(value, &color_value, false, path, lineno, "colors", key))
  629. return false;
  630. *color = color_value;
  631. return true;
  632. }
  633. static bool
  634. parse_section_cursor(const char *key, const char *value, struct config *conf,
  635. const char *path, unsigned lineno)
  636. {
  637. if (strcmp(key, "style") == 0) {
  638. if (strcmp(value, "block") == 0)
  639. conf->cursor.style = CURSOR_BLOCK;
  640. else if (strcmp(value, "bar") == 0)
  641. conf->cursor.style = CURSOR_BAR;
  642. else if (strcmp(value, "underline") == 0)
  643. conf->cursor.style = CURSOR_UNDERLINE;
  644. else {
  645. LOG_AND_NOTIFY_ERR("%s:%d: invalid 'style': %s", path, lineno, value);
  646. return false;
  647. }
  648. }
  649. else if (strcmp(key, "blink") == 0)
  650. conf->cursor.blink = str_to_bool(value);
  651. else if (strcmp(key, "color") == 0) {
  652. char *value_copy = xstrdup(value);
  653. const char *text = strtok(value_copy, " ");
  654. const char *cursor = strtok(NULL, " ");
  655. uint32_t text_color, cursor_color;
  656. if (text == NULL || cursor == NULL ||
  657. !str_to_color(text, &text_color, false, path, lineno, "cursor", "color") ||
  658. !str_to_color(cursor, &cursor_color, false, path, lineno, "cursor", "color"))
  659. {
  660. LOG_AND_NOTIFY_ERR("%s:%d: invalid cursor colors: %s", path, lineno, value);
  661. free(value_copy);
  662. return false;
  663. }
  664. conf->cursor.color.text = 1u << 31 | text_color;
  665. conf->cursor.color.cursor = 1u << 31 | cursor_color;
  666. free(value_copy);
  667. }
  668. else {
  669. LOG_AND_NOTIFY_ERR("%s:%d: [cursor]: %s: invalid key", path, lineno, key);
  670. return false;
  671. }
  672. return true;
  673. }
  674. static bool
  675. parse_section_mouse(const char *key, const char *value, struct config *conf,
  676. const char *path, unsigned lineno)
  677. {
  678. if (strcmp(key, "hide-when-typing") == 0)
  679. conf->mouse.hide_when_typing = str_to_bool(value);
  680. else if (strcmp(key, "alternate-scroll-mode") == 0)
  681. conf->mouse.alternate_scroll_mode = str_to_bool(value);
  682. else {
  683. LOG_AND_NOTIFY_ERR("%s:%d: [mouse]: %s: invalid key", path, lineno, key);
  684. return false;
  685. }
  686. return true;
  687. }
  688. static bool
  689. parse_section_csd(const char *key, const char *value, struct config *conf,
  690. const char *path, unsigned lineno)
  691. {
  692. if (strcmp(key, "preferred") == 0) {
  693. if (strcmp(value, "server") == 0)
  694. conf->csd.preferred = CONF_CSD_PREFER_SERVER;
  695. else if (strcmp(value, "client") == 0)
  696. conf->csd.preferred = CONF_CSD_PREFER_CLIENT;
  697. else if (strcmp(value, "none") == 0)
  698. conf->csd.preferred = CONF_CSD_PREFER_NONE;
  699. else {
  700. LOG_AND_NOTIFY_ERR(
  701. "%s:%d: csd.preferred: expected either "
  702. "'server', 'client' or 'none'", path, lineno);
  703. return false;
  704. }
  705. }
  706. else if (strcmp(key, "color") == 0) {
  707. uint32_t color;
  708. if (!str_to_color(value, &color, true, path, lineno, "csd", "color")) {
  709. LOG_AND_NOTIFY_ERR("%s:%d: invalid titlebar-color: %s", path, lineno, value);
  710. return false;
  711. }
  712. conf->csd.color.title_set = true;
  713. conf->csd.color.title = color;
  714. }
  715. else if (strcmp(key, "size") == 0) {
  716. unsigned long pixels;
  717. if (!str_to_ulong(value, 10, &pixels)) {
  718. LOG_AND_NOTIFY_ERR("%s:%d: expected an integer, got '%s'", path, lineno, value);
  719. return false;
  720. }
  721. conf->csd.title_height = pixels;
  722. }
  723. else if (strcmp(key, "button-width") == 0) {
  724. unsigned long pixels;
  725. if (!str_to_ulong(value, 10, &pixels)) {
  726. LOG_AND_NOTIFY_ERR("%s:%d: expected an integer, got '%s'", path, lineno, value);
  727. return false;
  728. }
  729. conf->csd.button_width = pixels;
  730. }
  731. else if (strcmp(key, "button-minimize-color") == 0) {
  732. uint32_t color;
  733. if (!str_to_color(value, &color, true, path, lineno, "csd", "button-minimize-color")) {
  734. LOG_AND_NOTIFY_ERR("%s:%d: invalid button-minimize-color: %s", path, lineno, value);
  735. return false;
  736. }
  737. conf->csd.color.minimize_set = true;
  738. conf->csd.color.minimize = color;
  739. }
  740. else if (strcmp(key, "button-maximize-color") == 0) {
  741. uint32_t color;
  742. if (!str_to_color(value, &color, true, path, lineno, "csd", "button-maximize-color")) {
  743. LOG_AND_NOTIFY_ERR("%s:%d: invalid button-maximize-color: %s", path, lineno, value);
  744. return false;
  745. }
  746. conf->csd.color.maximize_set = true;
  747. conf->csd.color.maximize = color;
  748. }
  749. else if (strcmp(key, "button-close-color") == 0) {
  750. uint32_t color;
  751. if (!str_to_color(value, &color, true, path, lineno, "csd", "button-close-color")) {
  752. LOG_AND_NOTIFY_ERR("%s:%d: invalid button-close-color: %s", path, lineno, value);
  753. return false;
  754. }
  755. conf->csd.color.close_set = true;
  756. conf->csd.color.close = color;
  757. }
  758. else {
  759. LOG_AND_NOTIFY_ERR("%s:%u: [csd]: %s: invalid action",
  760. path, lineno, key);
  761. return false;
  762. }
  763. return true;
  764. }
  765. /* Struct that holds temporary key/mouse binding parsed data */
  766. struct key_combo {
  767. char *text; /* Raw text, e.g. "Control+Shift+V" */
  768. struct config_key_modifiers modifiers;
  769. union {
  770. xkb_keysym_t sym; /* Key converted to an XKB symbol, e.g. XKB_KEY_V */
  771. struct {
  772. int button;
  773. int count;
  774. } m;
  775. };
  776. };
  777. typedef tll(struct key_combo) key_combo_list_t;
  778. static void
  779. free_key_combo_list(key_combo_list_t *key_combos)
  780. {
  781. tll_foreach(*key_combos, it)
  782. free(it->item.text);
  783. tll_free(*key_combos);
  784. }
  785. static bool
  786. parse_modifiers(struct config *conf, const char *text, size_t len,
  787. struct config_key_modifiers *modifiers, const char *path, unsigned lineno)
  788. {
  789. bool ret = false;
  790. *modifiers = (struct config_key_modifiers){0};
  791. char *copy = xstrndup(text, len);
  792. for (char *tok_ctx = NULL, *key = strtok_r(copy, "+", &tok_ctx);
  793. key != NULL;
  794. key = strtok_r(NULL, "+", &tok_ctx))
  795. {
  796. if (strcmp(key, XKB_MOD_NAME_SHIFT) == 0)
  797. modifiers->shift = true;
  798. else if (strcmp(key, XKB_MOD_NAME_CTRL) == 0)
  799. modifiers->ctrl = true;
  800. else if (strcmp(key, XKB_MOD_NAME_ALT) == 0)
  801. modifiers->alt = true;
  802. else if (strcmp(key, XKB_MOD_NAME_LOGO) == 0)
  803. modifiers->meta = true;
  804. else {
  805. LOG_AND_NOTIFY_ERR("%s:%d: %s: not a valid modifier name",
  806. path, lineno, key);
  807. goto out;
  808. }
  809. }
  810. ret = true;
  811. out:
  812. free(copy);
  813. return ret;
  814. }
  815. static bool
  816. parse_key_combos(struct config *conf, const char *combos, key_combo_list_t *key_combos,
  817. const char *path, unsigned lineno)
  818. {
  819. assert(tll_length(*key_combos) == 0);
  820. char *copy = xstrdup(combos);
  821. for (char *tok_ctx = NULL, *combo = strtok_r(copy, " ", &tok_ctx);
  822. combo != NULL;
  823. combo = strtok_r(NULL, " ", &tok_ctx))
  824. {
  825. struct config_key_modifiers modifiers = {0};
  826. const char *key = strrchr(combo, '+');
  827. if (key == NULL) {
  828. /* No modifiers */
  829. key = combo;
  830. } else {
  831. if (!parse_modifiers(conf, combo, key - combo, &modifiers, path, lineno))
  832. goto err;
  833. key++; /* Skip past the '+' */
  834. }
  835. /* Translate key name to symbol */
  836. xkb_keysym_t sym = xkb_keysym_from_name(key, 0);
  837. if (sym == XKB_KEY_NoSymbol) {
  838. LOG_AND_NOTIFY_ERR("%s:%d: %s: key is not a valid XKB key name",
  839. path, lineno, key);
  840. goto err;
  841. }
  842. tll_push_back(
  843. *key_combos,
  844. ((struct key_combo){.text = xstrdup(combo), .modifiers = modifiers, .sym = sym}));
  845. }
  846. free(copy);
  847. return true;
  848. err:
  849. tll_foreach(*key_combos, it)
  850. free(it->item.text);
  851. tll_free(*key_combos);
  852. free(copy);
  853. return false;
  854. }
  855. static bool
  856. has_key_binding_collisions(struct config *conf, const key_combo_list_t *key_combos,
  857. const char *path, unsigned lineno)
  858. {
  859. tll_foreach(conf->bindings.key, it) {
  860. tll_foreach(*key_combos, it2) {
  861. const struct config_key_modifiers *mods1 = &it->item.modifiers;
  862. const struct config_key_modifiers *mods2 = &it2->item.modifiers;
  863. if (memcmp(mods1, mods2, sizeof(*mods1)) == 0 &&
  864. it->item.sym == it2->item.sym)
  865. {
  866. bool has_pipe = it->item.pipe.cmd != NULL;
  867. LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s%s%s%s'",
  868. path, lineno, it2->item.text,
  869. binding_action_map[it->item.action],
  870. has_pipe ? " [" : "",
  871. has_pipe ? it->item.pipe.cmd : "",
  872. has_pipe ? "]" : "");
  873. return true;
  874. }
  875. }
  876. }
  877. return false;
  878. }
  879. static bool
  880. has_search_binding_collisions(struct config *conf, const key_combo_list_t *key_combos,
  881. const char *path, unsigned lineno)
  882. {
  883. tll_foreach(conf->bindings.search, it) {
  884. tll_foreach(*key_combos, it2) {
  885. const struct config_key_modifiers *mods1 = &it->item.modifiers;
  886. const struct config_key_modifiers *mods2 = &it2->item.modifiers;
  887. if (memcmp(mods1, mods2, sizeof(*mods1)) == 0 &&
  888. it->item.sym == it2->item.sym)
  889. {
  890. LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s'",
  891. path, lineno, it2->item.text,
  892. search_binding_action_map[it->item.action]);
  893. return true;
  894. }
  895. }
  896. }
  897. return false;
  898. }
  899. static int
  900. argv_compare(char *const *argv1, char *const *argv2)
  901. {
  902. assert(argv1 != NULL);
  903. assert(argv2 != NULL);
  904. for (size_t i = 0; ; i++) {
  905. if (argv1[i] == NULL && argv2[i] == NULL)
  906. return 0;
  907. if (argv1[i] == NULL)
  908. return -1;
  909. if (argv2[i] == NULL)
  910. return 1;
  911. int ret = strcmp(argv1[i], argv2[i]);
  912. if (ret != 0)
  913. return ret;
  914. }
  915. assert(false);
  916. return 1;
  917. }
  918. static void
  919. maybe_deprecated_key_binding(struct config *conf,
  920. const char *section,
  921. enum bind_action_normal action,
  922. const char *path, unsigned lineno)
  923. {
  924. enum bind_action_normal replacement = BIND_ACTION_NONE;
  925. switch (action) {
  926. case BIND_ACTION_SCROLLBACK_UP:
  927. replacement = BIND_ACTION_SCROLLBACK_UP_PAGE;
  928. break;
  929. case BIND_ACTION_SCROLLBACK_DOWN:
  930. replacement = BIND_ACTION_SCROLLBACK_DOWN_PAGE;
  931. break;
  932. default:
  933. return;
  934. }
  935. LOG_WARN("deprecated: %s:%d: [%s]: key binding %s, use %s instead",
  936. path, lineno, section,
  937. binding_action_map[action], binding_action_map[replacement]);
  938. const char fmt[] = "%s:%d: [%s]: \033[1m%s\033[21m, use \033[1m%s\033[21m instead";
  939. char *text = xasprintf(
  940. fmt, path, lineno, section,
  941. binding_action_map[action], binding_action_map[replacement]);
  942. struct user_notification deprecation = {
  943. .kind = USER_NOTIFICATION_DEPRECATED,
  944. .text = text,
  945. };
  946. tll_push_back(conf->notifications, deprecation);
  947. }
  948. static bool
  949. parse_section_key_bindings(
  950. const char *key, const char *value, struct config *conf,
  951. const char *path, unsigned lineno)
  952. {
  953. char *pipe_cmd = NULL;
  954. char **pipe_argv = NULL;
  955. size_t pipe_len = 0;
  956. if (value[0] == '[') {
  957. const char *pipe_cmd_end = strrchr(value, ']');
  958. if (pipe_cmd_end == NULL) {
  959. LOG_AND_NOTIFY_ERR("%s:%d: unclosed '['", path, lineno);
  960. return false;
  961. }
  962. pipe_len = pipe_cmd_end - value - 1;
  963. pipe_cmd = xstrndup(&value[1], pipe_len);
  964. if (!tokenize_cmdline(pipe_cmd, &pipe_argv)) {
  965. LOG_AND_NOTIFY_ERR("%s:%d: syntax error in command line", path, lineno);
  966. free(pipe_cmd);
  967. return false;
  968. }
  969. value = pipe_cmd_end + 1;
  970. while (isspace(*value))
  971. value++;
  972. }
  973. for (enum bind_action_normal action = 0;
  974. action < BIND_ACTION_KEY_COUNT;
  975. action++)
  976. {
  977. if (binding_action_map[action] == NULL)
  978. continue;
  979. if (strcmp(key, binding_action_map[action]) != 0)
  980. continue;
  981. maybe_deprecated_key_binding(
  982. conf, "key-bindings", action, path, lineno);
  983. /* Unset binding */
  984. if (strcasecmp(value, "none") == 0) {
  985. tll_foreach(conf->bindings.key, it) {
  986. if (it->item.action == action) {
  987. if (it->item.pipe.master_copy) {
  988. free(it->item.pipe.cmd);
  989. free(it->item.pipe.argv);
  990. }
  991. tll_remove(conf->bindings.key, it);
  992. }
  993. }
  994. free(pipe_argv);
  995. free(pipe_cmd);
  996. return true;
  997. }
  998. key_combo_list_t key_combos = tll_init();
  999. if (!parse_key_combos(conf, value, &key_combos, path, lineno) ||
  1000. has_key_binding_collisions(conf, &key_combos, path, lineno))
  1001. {
  1002. free(pipe_argv);
  1003. free(pipe_cmd);
  1004. free_key_combo_list(&key_combos);
  1005. return false;
  1006. }
  1007. /* Remove existing bindings for this action+pipe */
  1008. tll_foreach(conf->bindings.key, it) {
  1009. if (it->item.action == action &&
  1010. ((it->item.pipe.argv == NULL && pipe_argv == NULL) ||
  1011. (it->item.pipe.argv != NULL && pipe_argv != NULL &&
  1012. argv_compare(it->item.pipe.argv, pipe_argv) == 0)))
  1013. {
  1014. if (it->item.pipe.master_copy) {
  1015. free(it->item.pipe.cmd);
  1016. free(it->item.pipe.argv);
  1017. }
  1018. tll_remove(conf->bindings.key, it);
  1019. }
  1020. }
  1021. /* Emit key bindings */
  1022. bool first = true;
  1023. tll_foreach(key_combos, it) {
  1024. struct config_key_binding_normal binding = {
  1025. .action = action,
  1026. .modifiers = it->item.modifiers,
  1027. .sym = it->item.sym,
  1028. .pipe = {
  1029. .cmd = pipe_cmd,
  1030. .argv = pipe_argv,
  1031. .master_copy = first,
  1032. },
  1033. };
  1034. tll_push_back(conf->bindings.key, binding);
  1035. first = false;
  1036. }
  1037. free_key_combo_list(&key_combos);
  1038. return true;
  1039. }
  1040. LOG_AND_NOTIFY_ERR("%s:%u: [key-bindings]: %s: invalid action",
  1041. path, lineno, key);
  1042. free(pipe_cmd);
  1043. free(pipe_argv);
  1044. return false;
  1045. }
  1046. static bool
  1047. parse_section_search_bindings(
  1048. const char *key, const char *value, struct config *conf,
  1049. const char *path, unsigned lineno)
  1050. {
  1051. for (enum bind_action_search action = 0;
  1052. action < BIND_ACTION_SEARCH_COUNT;
  1053. action++)
  1054. {
  1055. if (search_binding_action_map[action] == NULL)
  1056. continue;
  1057. if (strcmp(key, search_binding_action_map[action]) != 0)
  1058. continue;
  1059. /* Unset binding */
  1060. if (strcasecmp(value, "none") == 0) {
  1061. tll_foreach(conf->bindings.search, it) {
  1062. if (it->item.action == action)
  1063. tll_remove(conf->bindings.search, it);
  1064. }
  1065. return true;
  1066. }
  1067. key_combo_list_t key_combos = tll_init();
  1068. if (!parse_key_combos(conf, value, &key_combos, path, lineno) ||
  1069. has_search_binding_collisions(conf, &key_combos, path, lineno))
  1070. {
  1071. free_key_combo_list(&key_combos);
  1072. return false;
  1073. }
  1074. /* Remove existing bindings for this action */
  1075. tll_foreach(conf->bindings.search, it) {
  1076. if (it->item.action == action)
  1077. tll_remove(conf->bindings.search, it);
  1078. }
  1079. /* Emit key bindings */
  1080. tll_foreach(key_combos, it) {
  1081. struct config_key_binding_search binding = {
  1082. .action = action,
  1083. .modifiers = it->item.modifiers,
  1084. .sym = it->item.sym,
  1085. };
  1086. tll_push_back(conf->bindings.search, binding);
  1087. }
  1088. free_key_combo_list(&key_combos);
  1089. return true;
  1090. }
  1091. LOG_AND_NOTIFY_ERR("%s:%u: [search-bindings]: %s: invalid key", path, lineno, key);
  1092. return false;
  1093. }
  1094. static bool
  1095. parse_mouse_combos(struct config *conf, const char *combos, key_combo_list_t *key_combos,
  1096. const char *path, unsigned lineno)
  1097. {
  1098. assert(tll_length(*key_combos) == 0);
  1099. char *copy = xstrdup(combos);
  1100. for (char *tok_ctx = NULL, *combo = strtok_r(copy, " ", &tok_ctx);
  1101. combo != NULL;
  1102. combo = strtok_r(NULL, " ", &tok_ctx))
  1103. {
  1104. struct config_key_modifiers modifiers = {0};
  1105. char *key = strrchr(combo, '+');
  1106. if (key == NULL) {
  1107. /* No modifiers */
  1108. key = combo;
  1109. } else {
  1110. *key = '\0';
  1111. if (!parse_modifiers(conf, combo, key - combo, &modifiers, path, lineno))
  1112. goto err;
  1113. if (modifiers.shift) {
  1114. LOG_AND_NOTIFY_ERR(
  1115. "%s:%d: Shift cannot be used in mouse bindings",
  1116. path, lineno);
  1117. goto err;
  1118. }
  1119. key++; /* Skip past the '+' */
  1120. }
  1121. size_t count = 0;
  1122. {
  1123. char *_count = strrchr(key, '-');
  1124. if (_count != NULL) {
  1125. *_count = '\0';
  1126. _count++;
  1127. errno = 0;
  1128. char *end;
  1129. unsigned long value = strtoul(_count, &end, 10);
  1130. if (_count[0] == '\0' || *end != '\0' || errno != 0) {
  1131. if (errno != 0)
  1132. LOG_AND_NOTIFY_ERRNO(
  1133. "%s:%d: %s: invalid click count", path, lineno, _count);
  1134. else
  1135. LOG_AND_NOTIFY_ERR(
  1136. "%s:%d: %s: invalid click count", path, lineno, _count);
  1137. goto err;
  1138. }
  1139. count = value;
  1140. }
  1141. }
  1142. static const struct {
  1143. const char *name;
  1144. int code;
  1145. } map[] = {
  1146. {"BTN_LEFT", BTN_LEFT},
  1147. {"BTN_RIGHT", BTN_RIGHT},
  1148. {"BTN_MIDDLE", BTN_MIDDLE},
  1149. {"BTN_SIDE", BTN_SIDE},
  1150. {"BTN_EXTRA", BTN_EXTRA},
  1151. {"BTN_FORWARD", BTN_FORWARD},
  1152. {"BTN_BACK", BTN_BACK},
  1153. {"BTN_TASK", BTN_TASK},
  1154. };
  1155. int button = 0;
  1156. for (size_t i = 0; i < ALEN(map); i++) {
  1157. if (strcmp(key, map[i].name) == 0) {
  1158. button = map[i].code;
  1159. break;
  1160. }
  1161. }
  1162. if (button == 0) {
  1163. LOG_AND_NOTIFY_ERR("%s:%d: %s: invalid mouse button name", path, lineno, key);
  1164. goto err;
  1165. }
  1166. struct key_combo new = {
  1167. .text = xstrdup(combo),
  1168. .modifiers = modifiers,
  1169. .m = {
  1170. .button = button,
  1171. .count = count,
  1172. },
  1173. };
  1174. tll_push_back(*key_combos, new);
  1175. }
  1176. free(copy);
  1177. return true;
  1178. err:
  1179. tll_foreach(*key_combos, it)
  1180. free(it->item.text);
  1181. tll_free(*key_combos);
  1182. free(copy);
  1183. return false;
  1184. }
  1185. static bool
  1186. has_mouse_binding_collisions(struct config *conf, const key_combo_list_t *key_combos,
  1187. const char *path, unsigned lineno)
  1188. {
  1189. tll_foreach(conf->bindings.mouse, it) {
  1190. tll_foreach(*key_combos, it2) {
  1191. const struct config_key_modifiers *mods1 = &it->item.modifiers;
  1192. const struct config_key_modifiers *mods2 = &it2->item.modifiers;
  1193. if (memcmp(mods1, mods2, sizeof(*mods1)) == 0 &&
  1194. it->item.button == it2->item.m.button &&
  1195. it->item.count == it2->item.m.count)
  1196. {
  1197. LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s'",
  1198. path, lineno, it2->item.text,
  1199. binding_action_map[it->item.action]);
  1200. return true;
  1201. }
  1202. }
  1203. }
  1204. return false;
  1205. }
  1206. static bool
  1207. parse_section_mouse_bindings(
  1208. const char *key, const char *value, struct config *conf,
  1209. const char *path, unsigned lineno)
  1210. {
  1211. for (enum bind_action_normal action = 0;
  1212. action < BIND_ACTION_COUNT;
  1213. action++)
  1214. {
  1215. if (binding_action_map[action] == NULL)
  1216. continue;
  1217. if (strcmp(key, binding_action_map[action]) != 0)
  1218. continue;
  1219. maybe_deprecated_key_binding(
  1220. conf, "mouse-bindings", action, path, lineno);
  1221. /* Unset binding */
  1222. if (strcasecmp(value, "none") == 0) {
  1223. tll_foreach(conf->bindings.mouse, it) {
  1224. if (it->item.action == action)
  1225. tll_remove(conf->bindings.mouse, it);
  1226. }
  1227. return true;
  1228. }
  1229. key_combo_list_t key_combos = tll_init();
  1230. if (!parse_mouse_combos(conf, value, &key_combos, path, lineno) ||
  1231. has_mouse_binding_collisions(conf, &key_combos, path, lineno))
  1232. {
  1233. free_key_combo_list(&key_combos);
  1234. return false;
  1235. }
  1236. /* Remove existing bindings for this action */
  1237. tll_foreach(conf->bindings.mouse, it) {
  1238. if (it->item.action == action) {
  1239. tll_remove(conf->bindings.mouse, it);
  1240. }
  1241. }
  1242. /* Emit mouse bindings */
  1243. tll_foreach(key_combos, it) {
  1244. struct config_mouse_binding binding = {
  1245. .action = action,
  1246. .modifiers = it->item.modifiers,
  1247. .button = it->item.m.button,
  1248. .count = it->item.m.count,
  1249. };
  1250. tll_push_back(conf->bindings.mouse, binding);
  1251. }
  1252. free_key_combo_list(&key_combos);
  1253. return true;
  1254. }
  1255. LOG_AND_NOTIFY_ERR("%s:%u: [mouse-bindings]: %s: invalid key", path, lineno, key);
  1256. return false;
  1257. }
  1258. static bool
  1259. parse_section_tweak(
  1260. const char *key, const char *value, struct config *conf,
  1261. const char *path, unsigned lineno)
  1262. {
  1263. if (strcmp(key, "scaling-filter") == 0) {
  1264. static const struct {
  1265. const char *name;
  1266. enum fcft_scaling_filter filter;
  1267. } filters[] = {
  1268. {"none", FCFT_SCALING_FILTER_NONE},
  1269. {"nearest", FCFT_SCALING_FILTER_NEAREST},
  1270. {"bilinear", FCFT_SCALING_FILTER_BILINEAR},
  1271. {"cubic", FCFT_SCALING_FILTER_CUBIC},
  1272. {"lanczos3", FCFT_SCALING_FILTER_LANCZOS3},
  1273. };
  1274. for (size_t i = 0; i < ALEN(filters); i++) {
  1275. if (strcmp(value, filters[i].name) == 0) {
  1276. conf->tweak.fcft_filter = filters[i].filter;
  1277. LOG_WARN("tweak: scaling-filter=%s", filters[i].name);
  1278. return true;
  1279. }
  1280. }
  1281. LOG_AND_NOTIFY_ERR(
  1282. "%s:%d: [tweak]: %s: invalid 'scaling-filter' value, "
  1283. "expected one of 'none', 'nearest', 'bilinear', 'cubic' or "
  1284. "'lanczos3'", path, lineno, value);
  1285. return false;
  1286. }
  1287. else if (strcmp(key, "allow-overflowing-double-width-glyphs") == 0) {
  1288. conf->tweak.allow_overflowing_double_width_glyphs = str_to_bool(value);
  1289. if (conf->tweak.allow_overflowing_double_width_glyphs)
  1290. LOG_WARN("tweak: allow overflowing double-width glyphs");
  1291. }
  1292. else if (strcmp(key, "damage-whole-window") == 0) {
  1293. conf->tweak.damage_whole_window = str_to_bool(value);
  1294. if (conf->tweak.damage_whole_window)
  1295. LOG_WARN("tweak: damage whole window");
  1296. }
  1297. else if (strcmp(key, "render-timer") == 0) {
  1298. if (strcmp(value, "none") == 0) {
  1299. conf->tweak.render_timer_osd = false;
  1300. conf->tweak.render_timer_log = false;
  1301. } else if (strcmp(value, "osd") == 0) {
  1302. conf->tweak.render_timer_osd = true;
  1303. conf->tweak.render_timer_log = false;
  1304. } else if (strcmp(value, "log") == 0) {
  1305. conf->tweak.render_timer_osd = false;
  1306. conf->tweak.render_timer_log = true;
  1307. } else if (strcmp(value, "both") == 0) {
  1308. conf->tweak.render_timer_osd = true;
  1309. conf->tweak.render_timer_log = true;
  1310. } else {
  1311. LOG_AND_NOTIFY_ERR(
  1312. "%s:%d: [tweak]: %s: invalid 'render-timer' value, "
  1313. "expected one of 'none', 'osd', 'log' or 'both'",
  1314. path, lineno, value);
  1315. return false;
  1316. }
  1317. }
  1318. else if (strcmp(key, "delayed-render-lower") == 0) {
  1319. unsigned long ns;
  1320. if (!str_to_ulong(value, 10, &ns)) {
  1321. LOG_AND_NOTIFY_ERR("%s:%d: expected an integer, got '%s'", path, lineno, value);
  1322. return false;
  1323. }
  1324. if (ns > 16666666) {
  1325. LOG_AND_NOTIFY_ERR("%s:%d: timeout must not exceed 16ms", path, lineno);
  1326. return false;
  1327. }
  1328. conf->tweak.delayed_render_lower_ns = ns;
  1329. LOG_WARN("tweak: delayed-render-lower=%lu", ns);
  1330. }
  1331. else if (strcmp(key, "delayed-render-upper") == 0) {
  1332. unsigned long ns;
  1333. if (!str_to_ulong(value, 10, &ns)) {
  1334. LOG_AND_NOTIFY_ERR("%s:%d: expected an integer, got '%s'", path, lineno, value);
  1335. return false;
  1336. }
  1337. if (ns > 16666666) {
  1338. LOG_AND_NOTIFY_ERR("%s:%d: timeout must not exceed 16ms", path, lineno);
  1339. return false;
  1340. }
  1341. conf->tweak.delayed_render_upper_ns = ns;
  1342. LOG_WARN("tweak: delayed-render-upper=%lu", ns);
  1343. }
  1344. else if (strcmp(key, "max-shm-pool-size-mb") == 0) {
  1345. unsigned long mb;
  1346. if (!str_to_ulong(value, 10, &mb)) {
  1347. LOG_AND_NOTIFY_ERR("%s:%d: expected an integer, got '%s'", path, lineno, value);
  1348. return false;
  1349. }
  1350. conf->tweak.max_shm_pool_size = min(mb * 1024 * 1024, INT32_MAX);
  1351. LOG_WARN("tweak: max-shm-pool-size=%lld bytes",
  1352. (long long)conf->tweak.max_shm_pool_size);
  1353. }
  1354. else {
  1355. LOG_AND_NOTIFY_ERR("%s:%u: [tweak]: %s: invalid key", path, lineno, key);
  1356. return false;
  1357. }
  1358. return true;
  1359. }
  1360. static bool
  1361. parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_are_fatal)
  1362. {
  1363. enum section {
  1364. SECTION_MAIN,
  1365. SECTION_SCROLLBACK,
  1366. SECTION_COLORS,
  1367. SECTION_CURSOR,
  1368. SECTION_MOUSE,
  1369. SECTION_CSD,
  1370. SECTION_KEY_BINDINGS,
  1371. SECTION_SEARCH_BINDINGS,
  1372. SECTION_MOUSE_BINDINGS,
  1373. SECTION_TWEAK,
  1374. SECTION_COUNT,
  1375. } section = SECTION_MAIN;
  1376. /* Function pointer, called for each key/value line */
  1377. typedef bool (*parser_fun_t)(
  1378. const char *key, const char *value, struct config *conf,
  1379. const char *path, unsigned lineno);
  1380. static const struct {
  1381. parser_fun_t fun;
  1382. const char *name;
  1383. } section_info[] = {
  1384. [SECTION_MAIN] = {&parse_section_main, "main"},
  1385. [SECTION_SCROLLBACK] = {&parse_section_scrollback, "scrollback"},
  1386. [SECTION_COLORS] = {&parse_section_colors, "colors"},
  1387. [SECTION_CURSOR] = {&parse_section_cursor, "cursor"},
  1388. [SECTION_MOUSE] = {&parse_section_mouse, "mouse"},
  1389. [SECTION_CSD] = {&parse_section_csd, "csd"},
  1390. [SECTION_KEY_BINDINGS] = {&parse_section_key_bindings, "key-bindings"},
  1391. [SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"},
  1392. [SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"},
  1393. [SECTION_TWEAK] = {&parse_section_tweak, "tweak"},
  1394. };
  1395. static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch");
  1396. unsigned lineno = 0;
  1397. char *_line = NULL;
  1398. size_t count = 0;
  1399. #define error_or_continue() \
  1400. { \
  1401. if (errors_are_fatal) \
  1402. goto err; \
  1403. else \
  1404. continue; \
  1405. }
  1406. while (true) {
  1407. errno = 0;
  1408. lineno++;
  1409. ssize_t ret = getline(&_line, &count, f);
  1410. if (ret < 0) {
  1411. if (errno != 0) {
  1412. LOG_AND_NOTIFY_ERRNO("failed to read from configuration");
  1413. if (errors_are_fatal)
  1414. goto err;
  1415. }
  1416. break;
  1417. }
  1418. /* Strip leading whitespace */
  1419. char *line = _line;
  1420. {
  1421. while (isspace(*line))
  1422. line++;
  1423. if (line[0] != '\0') {
  1424. char *end = line + strlen(line) - 1;
  1425. while (isspace(*end))
  1426. end--;
  1427. *(end + 1) = '\0';
  1428. }
  1429. }
  1430. /* Empty line, or comment */
  1431. if (line[0] == '\0' || line[0] == '#')
  1432. continue;
  1433. /* Split up into key/value pair + trailing comment */
  1434. char *key_value = strtok(line, "#");
  1435. char UNUSED *comment = strtok(NULL, "\n");
  1436. /* Check for new section */
  1437. if (key_value[0] == '[') {
  1438. char *end = strchr(key_value, ']');
  1439. if (end == NULL) {
  1440. LOG_AND_NOTIFY_ERR("%s:%d: syntax error: %s", path, lineno, key_value);
  1441. error_or_continue();
  1442. }
  1443. *end = '\0';
  1444. section = SECTION_COUNT;
  1445. for (enum section i = 0; i < SECTION_COUNT; i++) {
  1446. if (strcmp(&key_value[1], section_info[i].name) == 0) {
  1447. section = i;
  1448. }
  1449. }
  1450. if (section == SECTION_COUNT) {
  1451. LOG_AND_NOTIFY_ERR("%s:%d: invalid section name: %s", path, lineno, &key_value[1]);
  1452. error_or_continue();
  1453. }
  1454. /* Process next line */
  1455. continue;
  1456. }
  1457. if (section >= SECTION_COUNT) {
  1458. /* Last section name was invalid; ignore all keys in it */
  1459. continue;
  1460. }
  1461. char *key = strtok(key_value, "=");
  1462. if (key == NULL) {
  1463. LOG_AND_NOTIFY_ERR("%s:%d: syntax error: no key specified", path, lineno);
  1464. error_or_continue();
  1465. }
  1466. char *value = strtok(NULL, "\n");
  1467. if (value == NULL) {
  1468. /* Empty value, i.e. "key=" */
  1469. value = key + strlen(key);
  1470. }
  1471. /* Strip trailing whitespace from key (leading stripped earlier) */
  1472. {
  1473. assert(!isspace(*key));
  1474. char *end = key + strlen(key) - 1;
  1475. while (isspace(*end))
  1476. end--;
  1477. *(end + 1) = '\0';
  1478. }
  1479. /* Strip leading+trailing whitespace from value */
  1480. {
  1481. while (isspace(*value))
  1482. value++;
  1483. if (value[0] != '\0') {
  1484. char *end = value + strlen(value) - 1;
  1485. while (isspace(*end))
  1486. end--;
  1487. *(end + 1) = '\0';
  1488. }
  1489. }
  1490. LOG_DBG("section=%s, key='%s', value='%s', comment='%s'",
  1491. section_info[section].name, key, value, comment);
  1492. assert(section >= 0 && section < SECTION_COUNT);
  1493. parser_fun_t section_parser = section_info[section].fun;
  1494. assert(section_parser != NULL);
  1495. if (!section_parser(key, value, conf, path, lineno))
  1496. error_or_continue();
  1497. }
  1498. free(_line);
  1499. return true;
  1500. err:
  1501. free(_line);
  1502. return false;
  1503. }
  1504. static char *
  1505. get_server_socket_path(void)
  1506. {
  1507. const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
  1508. if (xdg_runtime == NULL)
  1509. return xstrdup("/tmp/foot.sock");
  1510. const char *wayland_display = getenv("WAYLAND_DISPLAY");
  1511. if (wayland_display == NULL) {
  1512. return xasprintf("%s/foot.sock", xdg_runtime);
  1513. }
  1514. return xasprintf("%s/foot-%s.sock", xdg_runtime, wayland_display);
  1515. }
  1516. static void
  1517. add_default_key_bindings(struct config *conf)
  1518. {
  1519. #define add_binding(action, mods, sym) \
  1520. do { \
  1521. tll_push_back( \
  1522. conf->bindings.key, \
  1523. ((struct config_key_binding_normal){action, mods, sym})); \
  1524. } while (0)
  1525. const struct config_key_modifiers shift = {.shift = true};
  1526. const struct config_key_modifiers ctrl = {.ctrl = true};
  1527. const struct config_key_modifiers ctrl_shift = {.ctrl = true, .shift = true};
  1528. add_binding(BIND_ACTION_SCROLLBACK_UP, shift, XKB_KEY_Page_Up);
  1529. add_binding(BIND_ACTION_SCROLLBACK_DOWN, shift, XKB_KEY_Page_Down);
  1530. add_binding(BIND_ACTION_CLIPBOARD_COPY, ctrl_shift, XKB_KEY_C);
  1531. add_binding(BIND_ACTION_CLIPBOARD_PASTE, ctrl_shift, XKB_KEY_V);
  1532. add_binding(BIND_ACTION_PRIMARY_PASTE, shift, XKB_KEY_Insert);
  1533. add_binding(BIND_ACTION_SEARCH_START, ctrl_shift, XKB_KEY_R);
  1534. add_binding(BIND_ACTION_FONT_SIZE_UP, ctrl, XKB_KEY_plus);
  1535. add_binding(BIND_ACTION_FONT_SIZE_UP, ctrl, XKB_KEY_equal);
  1536. add_binding(BIND_ACTION_FONT_SIZE_UP, ctrl, XKB_KEY_KP_Add);
  1537. add_binding(BIND_ACTION_FONT_SIZE_DOWN, ctrl, XKB_KEY_minus);
  1538. add_binding(BIND_ACTION_FONT_SIZE_DOWN, ctrl, XKB_KEY_KP_Subtract);
  1539. add_binding(BIND_ACTION_FONT_SIZE_RESET, ctrl, XKB_KEY_0);
  1540. add_binding(BIND_ACTION_FONT_SIZE_RESET, ctrl, XKB_KEY_KP_0);
  1541. add_binding(BIND_ACTION_SPAWN_TERMINAL, ctrl_shift, XKB_KEY_N);
  1542. #undef add_binding
  1543. }
  1544. static void
  1545. add_default_search_bindings(struct config *conf)
  1546. {
  1547. #define add_binding(action, mods, sym) \
  1548. do { \
  1549. tll_push_back( \
  1550. conf->bindings.search, \
  1551. ((struct config_key_binding_search){action, mods, sym})); \
  1552. } while (0)
  1553. const struct config_key_modifiers none = {0};
  1554. const struct config_key_modifiers alt = {.alt = true};
  1555. const struct config_key_modifiers ctrl = {.ctrl = true};
  1556. const struct config_key_modifiers ctrl_shift = {.ctrl = true, .shift = true};
  1557. add_binding(BIND_ACTION_SEARCH_CANCEL, ctrl, XKB_KEY_g);
  1558. add_binding(BIND_ACTION_SEARCH_CANCEL, none, XKB_KEY_Escape);
  1559. add_binding(BIND_ACTION_SEARCH_COMMIT, none, XKB_KEY_Return);
  1560. add_binding(BIND_ACTION_SEARCH_FIND_PREV, ctrl, XKB_KEY_r);
  1561. add_binding(BIND_ACTION_SEARCH_FIND_NEXT, ctrl, XKB_KEY_s);
  1562. add_binding(BIND_ACTION_SEARCH_EDIT_LEFT, none, XKB_KEY_Left);
  1563. add_binding(BIND_ACTION_SEARCH_EDIT_LEFT, ctrl, XKB_KEY_b);
  1564. add_binding(BIND_ACTION_SEARCH_EDIT_LEFT_WORD, ctrl, XKB_KEY_Left);
  1565. add_binding(BIND_ACTION_SEARCH_EDIT_LEFT_WORD, alt, XKB_KEY_b);
  1566. add_binding(BIND_ACTION_SEARCH_EDIT_RIGHT, none, XKB_KEY_Right);
  1567. add_binding(BIND_ACTION_SEARCH_EDIT_RIGHT, ctrl, XKB_KEY_f);
  1568. add_binding(BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, ctrl, XKB_KEY_Right);
  1569. add_binding(BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, alt, XKB_KEY_f);
  1570. add_binding(BIND_ACTION_SEARCH_EDIT_HOME, none, XKB_KEY_Home);
  1571. add_binding(BIND_ACTION_SEARCH_EDIT_HOME, ctrl, XKB_KEY_a);
  1572. add_binding(BIND_ACTION_SEARCH_EDIT_END, none, XKB_KEY_End);
  1573. add_binding(BIND_ACTION_SEARCH_EDIT_END, ctrl, XKB_KEY_e);
  1574. add_binding(BIND_ACTION_SEARCH_DELETE_PREV, none, XKB_KEY_BackSpace);
  1575. add_binding(BIND_ACTION_SEARCH_DELETE_PREV_WORD, ctrl, XKB_KEY_BackSpace);
  1576. add_binding(BIND_ACTION_SEARCH_DELETE_PREV_WORD, alt, XKB_KEY_BackSpace);
  1577. add_binding(BIND_ACTION_SEARCH_DELETE_NEXT, none, XKB_KEY_Delete);
  1578. add_binding(BIND_ACTION_SEARCH_DELETE_NEXT_WORD, ctrl, XKB_KEY_Delete);
  1579. add_binding(BIND_ACTION_SEARCH_DELETE_NEXT_WORD, alt, XKB_KEY_d);
  1580. add_binding(BIND_ACTION_SEARCH_EXTEND_WORD, ctrl, XKB_KEY_w);
  1581. add_binding(BIND_ACTION_SEARCH_EXTEND_WORD_WS, ctrl_shift, XKB_KEY_W);
  1582. #undef add_binding
  1583. }
  1584. static void
  1585. add_default_mouse_bindings(struct config *conf)
  1586. {
  1587. #define add_binding(action, mods, btn, count) \
  1588. do { \
  1589. tll_push_back( \
  1590. conf->bindings.mouse, \
  1591. ((struct config_mouse_binding){action, mods, btn, count})); \
  1592. } while (0)
  1593. const struct config_key_modifiers none = {0};
  1594. const struct config_key_modifiers ctrl = {.ctrl = true};
  1595. add_binding(BIND_ACTION_PRIMARY_PASTE, none, BTN_MIDDLE, 1);
  1596. add_binding(BIND_ACTION_SELECT_BEGIN, none, BTN_LEFT, 1);
  1597. add_binding(BIND_ACTION_SELECT_BEGIN_BLOCK, ctrl, BTN_LEFT, 1);
  1598. add_binding(BIND_ACTION_SELECT_EXTEND, none, BTN_RIGHT, 1);
  1599. add_binding(BIND_ACTION_SELECT_WORD, none, BTN_LEFT, 2);
  1600. add_binding(BIND_ACTION_SELECT_WORD_WS, ctrl, BTN_LEFT, 2);
  1601. add_binding(BIND_ACTION_SELECT_ROW, none, BTN_LEFT, 3);
  1602. #undef add_binding
  1603. }
  1604. bool
  1605. config_load(struct config *conf, const char *conf_path,
  1606. user_notifications_t *initial_user_notifications, bool errors_are_fatal)
  1607. {
  1608. bool ret = false;
  1609. *conf = (struct config) {
  1610. .term = xstrdup("foot"),
  1611. .shell = get_shell(),
  1612. .title = xstrdup("foot"),
  1613. .app_id = xstrdup("foot"),
  1614. .word_delimiters = xwcsdup(L",│`|:\"'()[]{}<>"),
  1615. .size = {
  1616. .type = CONF_SIZE_PX,
  1617. .px = {
  1618. .width = 700,
  1619. .height = 500,
  1620. },
  1621. },
  1622. .pad_x = 2,
  1623. .pad_y = 2,
  1624. .bell_is_urgent = false,
  1625. .startup_mode = STARTUP_WINDOWED,
  1626. .fonts = tll_init(),
  1627. .scrollback = {
  1628. .lines = 1000,
  1629. .indicator = {
  1630. .position = SCROLLBACK_INDICATOR_POSITION_RELATIVE,
  1631. .format = SCROLLBACK_INDICATOR_FORMAT_TEXT,
  1632. .text = wcsdup(L""),
  1633. },
  1634. .multiplier = 3.,
  1635. },
  1636. .colors = {
  1637. .fg = default_foreground,
  1638. .bg = default_background,
  1639. .regular = {
  1640. default_regular[0],
  1641. default_regular[1],
  1642. default_regular[2],
  1643. default_regular[3],
  1644. default_regular[4],
  1645. default_regular[5],
  1646. default_regular[6],
  1647. default_regular[7],
  1648. },
  1649. .bright = {
  1650. default_bright[0],
  1651. default_bright[1],
  1652. default_bright[2],
  1653. default_bright[3],
  1654. default_bright[4],
  1655. default_bright[5],
  1656. default_bright[6],
  1657. default_bright[7],
  1658. },
  1659. .alpha = 0xffff,
  1660. .selection_fg = 0x80000000, /* Use default bg */
  1661. .selection_bg = 0x80000000, /* Use default fg */
  1662. .selection_uses_custom_colors = false,
  1663. },
  1664. .cursor = {
  1665. .style = CURSOR_BLOCK,
  1666. .blink = false,
  1667. .color = {
  1668. .text = 0,
  1669. .cursor = 0,
  1670. },
  1671. },
  1672. .mouse = {
  1673. .hide_when_typing = false,
  1674. .alternate_scroll_mode = true,
  1675. },
  1676. .csd = {
  1677. .preferred = CONF_CSD_PREFER_SERVER,
  1678. .title_height = 26,
  1679. .border_width = 5,
  1680. .button_width = 26,
  1681. },
  1682. .render_worker_count = sysconf(_SC_NPROCESSORS_ONLN),
  1683. .server_socket_path = get_server_socket_path(),
  1684. .presentation_timings = false,
  1685. .hold_at_exit = false,
  1686. .tweak = {
  1687. .fcft_filter = FCFT_SCALING_FILTER_LANCZOS3,
  1688. .allow_overflowing_double_width_glyphs = false,
  1689. .delayed_render_lower_ns = 500000, /* 0.5ms */
  1690. .delayed_render_upper_ns = 16666666 / 2, /* half a frame period (60Hz) */
  1691. .max_shm_pool_size = 512 * 1024 * 1024,
  1692. .render_timer_osd = false,
  1693. .render_timer_log = false,
  1694. .damage_whole_window = false,
  1695. },
  1696. .notifications = tll_init(),
  1697. };
  1698. tll_foreach(*initial_user_notifications, it)
  1699. tll_push_back(conf->notifications, it->item);
  1700. tll_free(*initial_user_notifications);
  1701. add_default_key_bindings(conf);
  1702. add_default_search_bindings(conf);
  1703. add_default_mouse_bindings(conf);
  1704. struct config_file conf_file = {.path = NULL, .fd = -1};
  1705. if (conf_path != NULL) {
  1706. int fd = open(conf_path, O_RDONLY);
  1707. if (fd < 0) {
  1708. LOG_AND_NOTIFY_ERRNO("%s: failed to open", conf_path);
  1709. ret = !errors_are_fatal;
  1710. goto out;
  1711. }
  1712. conf_file.path = xstrdup(conf_path);
  1713. conf_file.fd = fd;
  1714. } else {
  1715. conf_file = open_config(conf);
  1716. if (conf_file.fd < 0) {
  1717. LOG_AND_NOTIFY_ERR("no configuration found, using defaults");
  1718. ret = !errors_are_fatal;
  1719. goto out;
  1720. }
  1721. }
  1722. assert(conf_file.path != NULL);
  1723. assert(conf_file.fd >= 0);
  1724. LOG_INFO("loading configuration from %s", conf_file.path);
  1725. FILE *f = fdopen(conf_file.fd, "r");
  1726. if (f == NULL) {
  1727. LOG_AND_NOTIFY_ERRNO("%s: failed to open", conf_file.path);
  1728. ret = !errors_are_fatal;
  1729. goto out;
  1730. }
  1731. ret = parse_config_file(f, conf, conf_file.path, errors_are_fatal);
  1732. fclose(f);
  1733. conf->colors.selection_uses_custom_colors =
  1734. conf->colors.selection_fg >> 24 == 0 &&
  1735. conf->colors.selection_bg >> 24 == 0;
  1736. out:
  1737. if (ret && tll_length(conf->fonts) == 0)
  1738. tll_push_back(conf->fonts, config_font_parse("monospace"));
  1739. free(conf_file.path);
  1740. if (conf_file.fd >= 0)
  1741. close(conf_file.fd);
  1742. return ret;
  1743. }
  1744. void
  1745. config_free(struct config conf)
  1746. {
  1747. free(conf.term);
  1748. free(conf.shell);
  1749. free(conf.title);
  1750. free(conf.app_id);
  1751. free(conf.word_delimiters);
  1752. free(conf.scrollback.indicator.text);
  1753. tll_foreach(conf.fonts, it)
  1754. config_font_destroy(&it->item);
  1755. tll_free(conf.fonts);
  1756. free(conf.server_socket_path);
  1757. tll_foreach(conf.bindings.key, it) {
  1758. if (it->item.pipe.master_copy) {
  1759. free(it->item.pipe.cmd);
  1760. free(it->item.pipe.argv);
  1761. }
  1762. }
  1763. tll_free(conf.bindings.key);
  1764. tll_free(conf.bindings.mouse);
  1765. tll_free(conf.bindings.search);
  1766. user_notifications_free(&conf.notifications);
  1767. }
  1768. struct config_font
  1769. config_font_parse(const char *pattern)
  1770. {
  1771. FcPattern *pat = FcNameParse((const FcChar8 *)pattern);
  1772. double pt_size = -1.0;
  1773. FcPatternGetDouble(pat, FC_SIZE, 0, &pt_size);
  1774. FcPatternRemove(pat, FC_SIZE, 0);
  1775. int px_size = -1;
  1776. FcPatternGetInteger(pat, FC_PIXEL_SIZE, 0, &px_size);
  1777. FcPatternRemove(pat, FC_PIXEL_SIZE, 0);
  1778. if (pt_size == -1. && px_size == -1)
  1779. pt_size = 8.0;
  1780. char *stripped_pattern = (char *)FcNameUnparse(pat);
  1781. FcPatternDestroy(pat);
  1782. return (struct config_font){
  1783. .pattern = stripped_pattern,
  1784. .pt_size = pt_size,
  1785. .px_size = px_size};
  1786. }
  1787. void
  1788. config_font_destroy(struct config_font *font)
  1789. {
  1790. if (font == NULL)
  1791. return;
  1792. free(font->pattern);
  1793. }