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.
 
 
 
 

517 lines
15 KiB

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <ctype.h>
  5. #include <stdbool.h>
  6. #include <locale.h>
  7. #include <getopt.h>
  8. #include <signal.h>
  9. #include <errno.h>
  10. #include <unistd.h>
  11. #include <sys/types.h>
  12. #include <sys/sysinfo.h>
  13. #include <sys/utsname.h>
  14. #include <fcntl.h>
  15. #include <fcft/fcft.h>
  16. #define LOG_MODULE "main"
  17. #define LOG_ENABLE_DBG 0
  18. #include "log.h"
  19. #include "config.h"
  20. #include "fdm.h"
  21. #include "reaper.h"
  22. #include "render.h"
  23. #include "server.h"
  24. #include "shm.h"
  25. #include "terminal.h"
  26. #include "version.h"
  27. #include "xmalloc.h"
  28. static volatile sig_atomic_t aborted = 0;
  29. static void
  30. sig_handler(int signo)
  31. {
  32. aborted = 1;
  33. }
  34. static void
  35. print_usage(const char *prog_name)
  36. {
  37. printf(
  38. "Usage: %s [OPTIONS...]\n"
  39. "Usage: %s [OPTIONS...] command [ARGS...]\n"
  40. "\n"
  41. "Options:\n"
  42. " -c,--config=PATH load configuration from PATH ($XDG_CONFIG_HOME/foot/foot.ini)\n"
  43. " --check-config verify configuration, exit with 0 if ok, otherwise exit with 1\n"
  44. " -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n"
  45. " -t,--term=TERM value to set the environment variable TERM to (foot)\n"
  46. " --title=TITLE initial window title (foot)\n"
  47. " -a,--app-id=ID window application ID (foot)\n"
  48. " --maximized start in maximized mode\n"
  49. " --fullscreen start in fullscreen mode\n"
  50. " --login-shell start shell as a login shell\n"
  51. " -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels (alternative to '--dimensions')\n"
  52. " -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters (alternative to '--geometry')\n"
  53. " -s,--server[=PATH] run as a server (use 'footclient' to start terminals).\n"
  54. " Without PATH, $XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock will be used.\n"
  55. " --hold remain open after child process exits\n"
  56. " -p,--print-pid=FILE|FD print PID to file or FD (only applicable in server mode)\n"
  57. " -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n"
  58. " -s,--log-no-syslog disable syslog logging (only applicable in server mode)\n"
  59. " -v,--version show the version number and quit\n",
  60. prog_name, prog_name);
  61. }
  62. bool
  63. locale_is_utf8(void)
  64. {
  65. assert(strlen(u8"ö") == 2);
  66. wchar_t w;
  67. if (mbtowc(&w, u8"ö", 2) != 2)
  68. return false;
  69. if (w != U'ö')
  70. return false;
  71. return true;
  72. }
  73. struct shutdown_context {
  74. struct terminal **term;
  75. int exit_code;
  76. };
  77. static void
  78. term_shutdown_cb(void *data, int exit_code)
  79. {
  80. struct shutdown_context *ctx = data;
  81. *ctx->term = NULL;
  82. ctx->exit_code = exit_code;
  83. }
  84. static bool
  85. print_pid(const char *pid_file, bool *unlink_at_exit)
  86. {
  87. LOG_DBG("printing PID to %s", pid_file);
  88. errno = 0;
  89. char *end;
  90. int pid_fd = strtoul(pid_file, &end, 10);
  91. if (errno != 0 || *end != '\0') {
  92. if ((pid_fd = open(pid_file,
  93. O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC,
  94. S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {
  95. LOG_ERRNO("%s: failed to open", pid_file);
  96. return false;
  97. } else
  98. *unlink_at_exit = true;
  99. }
  100. if (pid_fd >= 0) {
  101. char pid[32];
  102. snprintf(pid, sizeof(pid), "%u\n", getpid());
  103. ssize_t bytes = write(pid_fd, pid, strlen(pid));
  104. close(pid_fd);
  105. if (bytes < 0) {
  106. LOG_ERRNO("failed to write PID to FD=%u", pid_fd);
  107. return false;
  108. }
  109. LOG_DBG("wrote %zd bytes to FD=%d", bytes, pid_fd);
  110. return true;
  111. } else
  112. return false;
  113. }
  114. int
  115. main(int argc, char *const *argv)
  116. {
  117. int ret = EXIT_FAILURE;
  118. /* Startup notifications; we don't support it, but must ensure we
  119. * don't pass this on to programs launched by us */
  120. unsetenv("DESKTOP_STARTUP_ID");
  121. const char *const prog_name = argv[0];
  122. static const struct option longopts[] = {
  123. {"config", required_argument, NULL, 'c'},
  124. {"check-config", no_argument, NULL, 'C'},
  125. {"term", required_argument, NULL, 't'},
  126. {"title", required_argument, NULL, 'T'},
  127. {"app-id", required_argument, NULL, 'a'},
  128. {"login-shell", no_argument, NULL, 'L'},
  129. {"font", required_argument, NULL, 'f'},
  130. {"geometry", required_argument, NULL, 'g'}, /* Deprecated */
  131. {"window-size-pixels", required_argument, NULL, 'w'},
  132. {"window-size-chars", required_argument, NULL, 'W'},
  133. {"server", optional_argument, NULL, 's'},
  134. {"hold", no_argument, NULL, 'H'},
  135. {"maximized", no_argument, NULL, 'm'},
  136. {"fullscreen", no_argument, NULL, 'F'},
  137. {"presentation-timings", no_argument, NULL, 'P'}, /* Undocumented */
  138. {"print-pid", required_argument, NULL, 'p'},
  139. {"log-colorize", optional_argument, NULL, 'l'},
  140. {"log-no-syslog", no_argument, NULL, 'S'},
  141. {"version", no_argument, NULL, 'v'},
  142. {"help", no_argument, NULL, 'h'},
  143. {NULL, no_argument, NULL, 0},
  144. };
  145. bool check_config = false;
  146. const char *conf_path = NULL;
  147. const char *conf_term = NULL;
  148. const char *conf_title = NULL;
  149. const char *conf_app_id = NULL;
  150. bool login_shell = false;
  151. tll(char *) conf_fonts = tll_init();
  152. enum conf_size_type conf_size_type = CONF_SIZE_PX;
  153. int conf_width = -1;
  154. int conf_height = -1;
  155. bool as_server = false;
  156. const char *conf_server_socket_path = NULL;
  157. bool presentation_timings = false;
  158. bool hold = false;
  159. bool maximized = false;
  160. bool fullscreen = false;
  161. bool unlink_pid_file = false;
  162. const char *pid_file = NULL;
  163. enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
  164. bool log_syslog = true;
  165. user_notifications_t user_notifications = tll_init();
  166. while (true) {
  167. int c = getopt_long(argc, argv, "+c:Ct:a:Lf:g:w:W:s::Pp:l::Svh", longopts, NULL);
  168. if (c == -1)
  169. break;
  170. switch (c) {
  171. case 'c':
  172. conf_path = optarg;
  173. break;
  174. case 'C':
  175. check_config = true;
  176. break;
  177. case 't':
  178. conf_term = optarg;
  179. break;
  180. case 'L':
  181. login_shell = true;
  182. break;
  183. case 'T':
  184. conf_title = optarg;
  185. break;
  186. case 'a':
  187. conf_app_id = optarg;
  188. break;
  189. case 'f':
  190. tll_free_and_free(conf_fonts, free);
  191. for (char *font = strtok(optarg, ","); font != NULL; font = strtok(NULL, ",")) {
  192. /* Strip leading spaces */
  193. while (*font != '\0' && isspace(*font))
  194. font++;
  195. /* Strip trailing spaces */
  196. char *end = font + strlen(font);
  197. assert(*end == '\0');
  198. end--;
  199. while (end > font && isspace(*end))
  200. *(end--) = '\0';
  201. if (strlen(font) == 0)
  202. continue;
  203. tll_push_back(conf_fonts, font);
  204. }
  205. break;
  206. case 'g': {
  207. LOG_WARN("deprecated: -g,--geometry command line option. Use -w,--window-size-pixels instead");
  208. struct user_notification deprecation = {
  209. .kind = USER_NOTIFICATION_DEPRECATED,
  210. .text = xstrdup(
  211. "\033[1m-g,--geometry\033[21m command line option. "
  212. "Use \033[1m-w,--window-size-pixels\033[21m instead"),
  213. };
  214. tll_push_back(user_notifications, deprecation);
  215. /* FALLTHROUGH */
  216. }
  217. case 'w': {
  218. unsigned width, height;
  219. if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
  220. fprintf(stderr, "error: invalid window-size-pixels: %s\n", optarg);
  221. return EXIT_FAILURE;
  222. }
  223. conf_size_type = CONF_SIZE_PX;
  224. conf_width = width;
  225. conf_height = height;
  226. break;
  227. }
  228. case 'W': {
  229. unsigned width, height;
  230. if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
  231. fprintf(stderr, "error: invalid window-size-chars: %s\n", optarg);
  232. return EXIT_FAILURE;
  233. }
  234. conf_size_type = CONF_SIZE_CELLS;
  235. conf_width = width;
  236. conf_height = height;
  237. break;
  238. }
  239. case 's':
  240. as_server = true;
  241. if (optarg != NULL)
  242. conf_server_socket_path = optarg;
  243. break;
  244. case 'P':
  245. presentation_timings = true;
  246. break;
  247. case 'H':
  248. hold = true;
  249. break;
  250. case 'm':
  251. maximized = true;
  252. fullscreen = false;
  253. break;
  254. case 'F':
  255. fullscreen = true;
  256. maximized = false;
  257. break;
  258. case 'p':
  259. pid_file = optarg;
  260. break;
  261. case 'l':
  262. if (optarg == NULL || strcmp(optarg, "auto") == 0)
  263. log_colorize = LOG_COLORIZE_AUTO;
  264. else if (strcmp(optarg, "never") == 0)
  265. log_colorize = LOG_COLORIZE_NEVER;
  266. else if (strcmp(optarg, "always") == 0)
  267. log_colorize = LOG_COLORIZE_ALWAYS;
  268. else {
  269. fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
  270. return EXIT_FAILURE;
  271. }
  272. break;
  273. case 'S':
  274. log_syslog = false;
  275. break;
  276. case 'v':
  277. printf("foot version %s\n", FOOT_VERSION);
  278. return EXIT_SUCCESS;
  279. case 'h':
  280. print_usage(prog_name);
  281. return EXIT_SUCCESS;
  282. case '?':
  283. return EXIT_FAILURE;
  284. }
  285. }
  286. log_init(log_colorize, as_server && log_syslog,
  287. as_server ? LOG_FACILITY_DAEMON : LOG_FACILITY_USER, LOG_CLASS_WARNING);
  288. argc -= optind;
  289. argv += optind;
  290. LOG_INFO("version: %s", FOOT_VERSION);
  291. {
  292. struct utsname name;
  293. if (uname(&name) < 0)
  294. LOG_ERRNO("uname() failed");
  295. else
  296. LOG_INFO("arch: %s/%zu-bit, ", name.machine, sizeof(void *) * 8);
  297. }
  298. setlocale(LC_CTYPE, "");
  299. LOG_INFO("locale: %s", setlocale(LC_CTYPE, NULL));
  300. if (!locale_is_utf8()) {
  301. LOG_ERR("locale is not UTF-8");
  302. return ret;
  303. }
  304. struct config conf = {NULL};
  305. if (!config_load(&conf, conf_path, &user_notifications, check_config)) {
  306. config_free(conf);
  307. return ret;
  308. }
  309. if (check_config) {
  310. config_free(conf);
  311. return EXIT_SUCCESS;
  312. }
  313. fcft_set_scaling_filter(conf.tweak.fcft_filter);
  314. if (conf_term != NULL) {
  315. free(conf.term);
  316. conf.term = xstrdup(conf_term);
  317. }
  318. if (conf_title != NULL) {
  319. free(conf.title);
  320. conf.title = xstrdup(conf_title);
  321. }
  322. if (conf_app_id != NULL) {
  323. free(conf.app_id);
  324. conf.app_id = xstrdup(conf_app_id);
  325. }
  326. if (login_shell)
  327. conf.login_shell = true;
  328. if (tll_length(conf_fonts) > 0) {
  329. tll_foreach(conf.fonts, it)
  330. config_font_destroy(&it->item);
  331. tll_free(conf.fonts);
  332. tll_foreach(conf_fonts, it)
  333. tll_push_back(conf.fonts, config_font_parse(it->item));
  334. tll_free(conf_fonts);
  335. }
  336. if (conf_width > 0 && conf_height > 0) {
  337. conf.size.type = conf_size_type;
  338. switch (conf_size_type) {
  339. case CONF_SIZE_PX:
  340. conf.size.px.width = conf_width;
  341. conf.size.px.height = conf_height;
  342. break;
  343. case CONF_SIZE_CELLS:
  344. conf.size.cells.width = conf_width;
  345. conf.size.cells.height = conf_height;
  346. break;
  347. }
  348. }
  349. if (conf_server_socket_path != NULL) {
  350. free(conf.server_socket_path);
  351. conf.server_socket_path = xstrdup(conf_server_socket_path);
  352. }
  353. if (maximized)
  354. conf.startup_mode = STARTUP_MAXIMIZED;
  355. else if (fullscreen)
  356. conf.startup_mode = STARTUP_FULLSCREEN;
  357. conf.presentation_timings = presentation_timings;
  358. conf.hold_at_exit = hold;
  359. struct fdm *fdm = NULL;
  360. struct reaper *reaper = NULL;
  361. struct wayland *wayl = NULL;
  362. struct renderer *renderer = NULL;
  363. struct terminal *term = NULL;
  364. struct server *server = NULL;
  365. struct shutdown_context shutdown_ctx = {.term = &term, .exit_code = EXIT_FAILURE};
  366. char *cwd = NULL;
  367. {
  368. errno = 0;
  369. size_t buf_len = 1024;
  370. do {
  371. cwd = xrealloc(cwd, buf_len);
  372. if (getcwd(cwd, buf_len) == NULL && errno != ERANGE) {
  373. LOG_ERRNO("failed to get current working directory");
  374. goto out;
  375. }
  376. buf_len *= 2;
  377. } while (errno == ERANGE);
  378. }
  379. shm_set_max_pool_size(conf.tweak.max_shm_pool_size);
  380. if ((fdm = fdm_init()) == NULL)
  381. goto out;
  382. if ((reaper = reaper_init(fdm)) == NULL)
  383. goto out;
  384. if ((wayl = wayl_init(&conf, fdm)) == NULL)
  385. goto out;
  386. if ((renderer = render_init(fdm, wayl)) == NULL)
  387. goto out;
  388. if (!as_server && (term = term_init(
  389. &conf, fdm, reaper, wayl, "foot", cwd, argc, argv,
  390. &term_shutdown_cb, &shutdown_ctx)) == NULL) {
  391. goto out;
  392. }
  393. free(cwd);
  394. cwd = NULL;
  395. if (as_server && (server = server_init(&conf, fdm, reaper, wayl)) == NULL)
  396. goto out;
  397. /* Remember to restore signals in slave */
  398. const struct sigaction sa = {.sa_handler = &sig_handler};
  399. if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
  400. LOG_ERRNO("failed to register signal handlers");
  401. goto out;
  402. }
  403. if (sigaction(SIGHUP, &(struct sigaction){.sa_handler = SIG_IGN}, NULL) < 0) {
  404. LOG_ERRNO("failed to ignore SIGHUP");
  405. goto out;
  406. }
  407. if (as_server)
  408. LOG_INFO("running as server; launch terminals by running footclient");
  409. if (as_server && pid_file != NULL) {
  410. if (!print_pid(pid_file, &unlink_pid_file))
  411. goto out;
  412. }
  413. while (!aborted && (as_server || tll_length(wayl->terms) > 0)) {
  414. if (!fdm_poll(fdm))
  415. break;
  416. }
  417. ret = aborted || tll_length(wayl->terms) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
  418. out:
  419. free(cwd);
  420. server_destroy(server);
  421. term_destroy(term);
  422. shm_fini();
  423. render_destroy(renderer);
  424. wayl_destroy(wayl);
  425. reaper_destroy(reaper);
  426. fdm_destroy(fdm);
  427. config_free(conf);
  428. if (unlink_pid_file)
  429. unlink(pid_file);
  430. LOG_INFO("goodbye");
  431. log_deinit();
  432. return ret == EXIT_SUCCESS && !as_server ? shutdown_ctx.exit_code : ret;
  433. }