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.
 
 
 
 

333 lines
10 KiB

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <stdint.h>
  5. #include <stdbool.h>
  6. #include <unistd.h>
  7. #include <assert.h>
  8. #include <getopt.h>
  9. #include <signal.h>
  10. #include <errno.h>
  11. #include <sys/socket.h>
  12. #include <linux/un.h>
  13. #define LOG_MODULE "foot-client"
  14. #define LOG_ENABLE_DBG 0
  15. #include "log.h"
  16. #include "version.h"
  17. #include "xmalloc.h"
  18. static volatile sig_atomic_t aborted = 0;
  19. static void
  20. sig_handler(int signo)
  21. {
  22. aborted = 1;
  23. }
  24. static void
  25. print_usage(const char *prog_name)
  26. {
  27. printf("Usage: %s [OPTIONS...]", prog_name);
  28. printf("Usage: %s [OPTIONS...] [ARGS...]\n", prog_name);
  29. printf("\n");
  30. printf("Options:\n");
  31. printf(" -t,--term=TERM value to set the environment variable TERM to (foot)\n"
  32. " --title=TITLE initial window title (foot)\n"
  33. " -a,--app-id=ID window application ID (foot)\n"
  34. " --maximized start in maximized mode\n"
  35. " --fullscreen start in fullscreen mode\n"
  36. " --login-shell start shell as a login shell\n"
  37. " -s,--server-socket=PATH path to the server UNIX domain socket (default=$XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)\n"
  38. " --hold remain open after child process exits\n"
  39. " -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n"
  40. " -v,--version show the version number and quit\n");
  41. }
  42. int
  43. main(int argc, char *const *argv)
  44. {
  45. int ret = EXIT_FAILURE;
  46. const char *const prog_name = argv[0];
  47. static const struct option longopts[] = {
  48. {"term", required_argument, NULL, 't'},
  49. {"title", required_argument, NULL, 'T'},
  50. {"app-id", required_argument, NULL, 'a'},
  51. {"maximized", no_argument, NULL, 'm'},
  52. {"fullscreen", no_argument, NULL, 'F'},
  53. {"login-shell", no_argument, NULL, 'L'},
  54. {"server-socket", required_argument, NULL, 's'},
  55. {"hold", no_argument, NULL, 'H'},
  56. {"log-colorize", optional_argument, NULL, 'l'},
  57. {"version", no_argument, NULL, 'v'},
  58. {"help", no_argument, NULL, 'h'},
  59. {NULL, no_argument, NULL, 0},
  60. };
  61. const char *term = "";
  62. const char *title = "";
  63. const char *app_id = "";
  64. const char *server_socket_path = NULL;
  65. enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
  66. bool login_shell = false;
  67. bool maximized = false;
  68. bool fullscreen = false;
  69. bool hold = false;
  70. while (true) {
  71. int c = getopt_long(argc, argv, "+:t:a:s:l::hv", longopts, NULL);
  72. if (c == -1)
  73. break;
  74. switch (c) {
  75. case 't':
  76. term = optarg;
  77. break;
  78. case 'T':
  79. title = optarg;
  80. break;
  81. case 'a':
  82. app_id = optarg;
  83. break;
  84. case 'L':
  85. login_shell = true;
  86. break;
  87. case ',':
  88. maximized = true;
  89. fullscreen = false;
  90. break;
  91. case 'F':
  92. fullscreen = true;
  93. maximized = false;
  94. break;
  95. case 's':
  96. server_socket_path = optarg;
  97. break;
  98. case 'H':
  99. hold = true;
  100. break;
  101. case 'l':
  102. if (optarg == NULL || strcmp(optarg, "auto") == 0)
  103. log_colorize = LOG_COLORIZE_AUTO;
  104. else if (strcmp(optarg, "never") == 0)
  105. log_colorize = LOG_COLORIZE_NEVER;
  106. else if (strcmp(optarg, "always") == 0)
  107. log_colorize = LOG_COLORIZE_ALWAYS;
  108. else {
  109. fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
  110. return EXIT_FAILURE;
  111. }
  112. break;
  113. case 'v':
  114. printf("footclient version %s\n", FOOT_VERSION);
  115. return EXIT_SUCCESS;
  116. case 'h':
  117. print_usage(prog_name);
  118. return EXIT_SUCCESS;
  119. case ':':
  120. fprintf(stderr, "error: -%c: missing required argument\n", optopt);
  121. return EXIT_FAILURE;
  122. case '?':
  123. fprintf(stderr, "error: -%c: invalid option\n", optopt);
  124. return EXIT_FAILURE;
  125. }
  126. }
  127. argc -= optind;
  128. argv += optind;
  129. log_init(log_colorize, false, LOG_FACILITY_USER, LOG_CLASS_WARNING);
  130. /* malloc:ed and needs to be in scope of all goto's */
  131. char *cwd = NULL;
  132. int fd = socket(AF_UNIX, SOCK_STREAM, 0);
  133. if (fd == -1) {
  134. LOG_ERRNO("failed to create socket");
  135. goto err;
  136. }
  137. struct sockaddr_un addr = {.sun_family = AF_UNIX};
  138. if (server_socket_path != NULL) {
  139. strncpy(addr.sun_path, server_socket_path, sizeof(addr.sun_path) - 1);
  140. if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
  141. LOG_ERR("%s: failed to connect (is 'foot --server' running?)", server_socket_path);
  142. goto err;
  143. }
  144. } else {
  145. bool connected = false;
  146. const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
  147. if (xdg_runtime != NULL) {
  148. const char *wayland_display = getenv("WAYLAND_DISPLAY");
  149. if (wayland_display != NULL)
  150. snprintf(addr.sun_path, sizeof(addr.sun_path),
  151. "%s/foot-%s.sock", xdg_runtime, wayland_display);
  152. else
  153. snprintf(addr.sun_path, sizeof(addr.sun_path),
  154. "%s/foot.sock", xdg_runtime);
  155. if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0)
  156. connected = true;
  157. else
  158. LOG_WARN("%s: failed to connect, will now try /tmp/foot.sock", addr.sun_path);
  159. }
  160. if (!connected) {
  161. strncpy(addr.sun_path, "/tmp/foot.sock", sizeof(addr.sun_path) - 1);
  162. if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
  163. LOG_ERRNO("failed to connect (is 'foot --server' running?)");
  164. goto err;
  165. }
  166. }
  167. }
  168. {
  169. errno = 0;
  170. size_t buf_len = 1024;
  171. do {
  172. cwd = xrealloc(cwd, buf_len);
  173. if (getcwd(cwd, buf_len) == NULL && errno != ERANGE) {
  174. LOG_ERRNO("failed to get current working directory");
  175. goto err;
  176. }
  177. buf_len *= 2;
  178. } while (errno == ERANGE);
  179. }
  180. const uint16_t cwd_len = strlen(cwd) + 1;
  181. const uint16_t term_len = strlen(term) + 1;
  182. const uint16_t title_len = strlen(title) + 1;
  183. const uint16_t app_id_len = strlen(app_id) + 1;
  184. uint32_t total_len = 0;
  185. /* Calculate total length */
  186. total_len += sizeof(cwd_len) + cwd_len;
  187. total_len += sizeof(term_len) + term_len;
  188. total_len += sizeof(title_len) + title_len;
  189. total_len += sizeof(app_id_len) + app_id_len;
  190. total_len += sizeof(uint8_t); /* maximized */
  191. total_len += sizeof(uint8_t); /* fullscreen */
  192. total_len += sizeof(uint8_t); /* hold */
  193. total_len += sizeof(uint8_t); /* login_shell */
  194. total_len += sizeof(argc);
  195. for (int i = 0; i < argc; i++) {
  196. uint16_t len = strlen(argv[i]) + 1;
  197. total_len += sizeof(len) + len;
  198. }
  199. LOG_DBG("term-len: %hu, argc: %d, total-len: %u",
  200. term_len, argc, total_len);
  201. if (send(fd, &total_len, sizeof(total_len), 0) != sizeof(total_len)) {
  202. LOG_ERRNO("failed to send total length to server");
  203. goto err;
  204. }
  205. if (send(fd, &cwd_len, sizeof(cwd_len), 0) != sizeof(cwd_len) ||
  206. send(fd, cwd, cwd_len, 0) != cwd_len)
  207. {
  208. LOG_ERRNO("failed to send CWD to server");
  209. goto err;
  210. }
  211. if (send(fd, &term_len, sizeof(term_len), 0) != sizeof(term_len) ||
  212. send(fd, term, term_len, 0) != term_len)
  213. {
  214. LOG_ERRNO("failed to send TERM to server");
  215. goto err;
  216. }
  217. if (send(fd, &title_len, sizeof(title_len), 0) != sizeof(title_len) ||
  218. send(fd, title, title_len, 0) != title_len)
  219. {
  220. LOG_ERRNO("failed to send title to server");
  221. goto err;
  222. }
  223. if (send(fd, &app_id_len, sizeof(app_id_len), 0) != sizeof(app_id_len) ||
  224. send(fd, app_id, app_id_len, 0) != app_id_len)
  225. {
  226. LOG_ERRNO("failed to send app-id to server");
  227. goto err;
  228. }
  229. if (send(fd, &(uint8_t){maximized}, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
  230. LOG_ERRNO("failed to send maximized");
  231. goto err;
  232. }
  233. if (send(fd, &(uint8_t){fullscreen}, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
  234. LOG_ERRNO("failed to send fullscreen");
  235. goto err;
  236. }
  237. if (send(fd, &(uint8_t){hold}, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
  238. LOG_ERRNO("failed to send hold");
  239. goto err;
  240. }
  241. if (send(fd, &(uint8_t){login_shell}, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
  242. LOG_ERRNO("failed to send login-shell");
  243. goto err;
  244. }
  245. LOG_DBG("argc = %d", argc);
  246. if (send(fd, &argc, sizeof(argc), 0) != sizeof(argc)) {
  247. LOG_ERRNO("failed to send argc/argv to server");
  248. goto err;
  249. }
  250. for (int i = 0; i < argc; i++) {
  251. uint16_t len = strlen(argv[i]) + 1;
  252. LOG_DBG("argv[%d] = %s (%hu)", i, argv[i], len);
  253. if (send(fd, &len, sizeof(len), 0) != sizeof(len) ||
  254. send(fd, argv[i], len, 0) != len)
  255. {
  256. LOG_ERRNO("failed to send argc/argv to server");
  257. goto err;
  258. }
  259. }
  260. const struct sigaction sa = {.sa_handler = &sig_handler};
  261. if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
  262. LOG_ERRNO("failed to register signal handlers");
  263. goto err;
  264. }
  265. int exit_code;
  266. ssize_t rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
  267. if (rcvd == -1 && errno == EINTR)
  268. assert(aborted);
  269. else if (rcvd != sizeof(exit_code))
  270. LOG_ERRNO("failed to read server response");
  271. else
  272. ret = exit_code;
  273. err:
  274. free(cwd);
  275. if (fd != -1)
  276. close(fd);
  277. log_deinit();
  278. return ret;
  279. }