qcmd.c 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. #include <asm-generic/ioctls.h>
  2. #define _GNU_SOURCE
  3. #include <libnotify/notification.h>
  4. #include <libnotify/notify.h>
  5. #include <glib-2.0/glib.h>
  6. #include <glib-2.0/glib-unix.h>
  7. #include <string.h>
  8. #include <stdlib.h>
  9. #include <unistd.h>
  10. #include <errno.h>
  11. #include <stdio.h>
  12. #include <fcntl.h>
  13. #include <signal.h>
  14. #include <sys/wait.h>
  15. #include <sys/ioctl.h>
  16. #include <getopt.h>
  17. #include "term.h"
  18. #define READ_BUF_SIZE 4096
  19. #ifdef DEBUG
  20. #define LOG(args...) do {printf("[qcmd] "); printf(args); printf("\n");} while(0)
  21. #else
  22. #define LOG(args...) do {} while(0)
  23. #endif
  24. struct qcmd {
  25. GMainLoop *loop;
  26. NotifyNotification *not;
  27. int cmd_pid;
  28. int cmd_fd;
  29. char *cmd;
  30. int close_on_exit;
  31. struct term terminal;
  32. int pango;
  33. };
  34. struct qcmd state = {0};
  35. static void clean_up()
  36. {
  37. g_object_unref(G_OBJECT(state.not));
  38. notify_uninit();
  39. close(state.cmd_fd);
  40. }
  41. void cmd_exit()
  42. {
  43. LOG("cmd_exit()");
  44. int status;
  45. pid_t pid = wait(&status);
  46. if (pid > 0) {
  47. if (WIFEXITED(status)) {
  48. if (WEXITSTATUS(status) != 0) {
  49. LOG("setting urgency to critical ...");
  50. notify_notification_set_urgency(state.not, NOTIFY_URGENCY_CRITICAL);
  51. LOG("urgency set");
  52. }
  53. }
  54. }
  55. if (state.close_on_exit) {
  56. notify_notification_close(state.not, NULL);
  57. }
  58. }
  59. static void exit_qcmd() {
  60. kill(state.cmd_pid, SIGKILL);
  61. cmd_exit();
  62. g_main_loop_quit(state.loop);
  63. }
  64. static void not_closed_callback(gpointer data)
  65. {
  66. LOG("not_closed_callback()");
  67. exit_qcmd();
  68. }
  69. static gboolean cmd_read_callback(gint fd, GIOCondition condition, gpointer user_data)
  70. {
  71. char read_buf[READ_BUF_SIZE];
  72. char *output;
  73. if (condition == G_IO_HUP) {
  74. return FALSE;
  75. }
  76. int size = 0;
  77. if ((size = read(fd, read_buf, READ_BUF_SIZE - 1)) > 0) {
  78. LOG("read %d bytes from pipe", size);
  79. read_buf[size] = '\0';
  80. term_put_string(&state.terminal, read_buf);
  81. }
  82. output = term_to_string(&state.terminal);
  83. notify_notification_update(state.not, state.cmd, output, NULL);
  84. notify_notification_show(state.not, NULL);
  85. free(output);
  86. return TRUE;
  87. }
  88. static int cmd_open(char *cmd, int *pid) {
  89. int pt = posix_openpt(O_RDWR | O_NOCTTY);
  90. int pts_fl = fcntl(pt, F_GETFL);
  91. if (-1 == pts_fl) {
  92. exit(1);
  93. }
  94. if (-1 == fcntl(pt, F_SETFL, pts_fl | O_NONBLOCK)) {
  95. exit(1);
  96. }
  97. struct winsize winsz = {
  98. .ws_row = state.terminal.num_rows,
  99. .ws_col = state.terminal.num_cols,
  100. };
  101. if (-1 == ioctl(pt, TIOCSWINSZ, &winsz)) {
  102. fprintf(stderr, "error: ioctl(): %s\n", strerror(errno));
  103. exit(1);
  104. }
  105. int p = fork();
  106. switch (p) {
  107. case -1:
  108. LOG("Error forking process.");
  109. exit(1);
  110. case 0:
  111. if (-1 == grantpt(pt)) {
  112. exit(1);
  113. }
  114. if (-1 == unlockpt(pt)) {
  115. exit(1);
  116. }
  117. char *pts_path = ptsname(pt);
  118. if (NULL == pts_path) {
  119. exit(1);
  120. }
  121. close(pt);
  122. if (-1 == setsid()) {
  123. fprintf(stderr, "error: setsid(): %s\n", strerror(errno));
  124. exit(1);
  125. }
  126. LOG("Opening terminal slave '%s'", pts_path);
  127. int pts_fd = open(pts_path, O_RDWR);
  128. if (-1 == pts_fd) {
  129. exit(1);
  130. }
  131. if (-1 == ioctl(pts_fd, TIOCSCTTY, 0)) {
  132. fprintf(stderr, "error: ioctl(): %s\n", strerror(errno));
  133. exit(1);
  134. }
  135. int pts_fl = fcntl(pts_fd, F_GETFL);
  136. if (-1 == pts_fl) {
  137. exit(1);
  138. }
  139. if (-1 == fcntl(pts_fd, F_SETFL, pts_fl | O_NONBLOCK)) {
  140. exit(1);
  141. }
  142. dup2(pts_fd, 0);
  143. dup2(pts_fd, 1);
  144. dup2(pts_fd, 2);
  145. close(pts_fd);
  146. execl("/bin/sh", "sh", "-c", cmd, (char *) NULL);
  147. exit(1);
  148. default:
  149. *pid = p;
  150. return pt;
  151. }
  152. }
  153. void print_version()
  154. {
  155. #ifdef VERSION
  156. printf("qcmd version %s\n", VERSION);
  157. #else
  158. printf("qcmd (unknown version)\n");
  159. #endif
  160. }
  161. void print_help()
  162. {
  163. printf(
  164. "usage: qcmd [options] [command]\n"
  165. " -h, --help Display this help text.\n"
  166. " -v, --version Display version information.\n"
  167. " -r Number of rows in terminal output.\n"
  168. " -c Number of columns in terminal output.\n"
  169. " -C, --close-on-exit Close the notification on command exit.\n"
  170. " -p, --pango Output as pango markup.\n"
  171. );
  172. }
  173. int main(int argc, char *argv[])
  174. {
  175. char *cmd;
  176. int rows = 10;
  177. int cols = 80;
  178. int c;
  179. int option_index = 0;
  180. int cmd_pid;
  181. int cmd_fd;
  182. while (1) {
  183. static struct option long_options[] = {
  184. {"help", no_argument, 0, 'h'},
  185. {"version", no_argument, 0, 'v'},
  186. {"close-on-exit", no_argument, 0, 'C'},
  187. {"pango", no_argument, 0, 'p'},
  188. {0, 0, 0, 0},
  189. };
  190. c = getopt_long(argc, argv, "hvCpc:r:", long_options, &option_index);
  191. if (c == -1) {
  192. break;
  193. }
  194. switch (c) {
  195. case 'h':
  196. print_help();
  197. return EXIT_SUCCESS;
  198. case 'v':
  199. print_version();
  200. return EXIT_SUCCESS;
  201. case 'C':
  202. state.close_on_exit = 1;
  203. break;
  204. case 'r':
  205. rows = atoi(optarg);
  206. break;
  207. case 'c':
  208. cols = atoi(optarg);
  209. break;
  210. case 'p':
  211. state.pango = 1;
  212. }
  213. }
  214. if (optind < argc) {
  215. cmd = argv[optind];
  216. }
  217. if (cmd == NULL || strlen(cmd) == 0) {
  218. printf("error: no command specified.\n");
  219. print_help();
  220. return EXIT_FAILURE;
  221. }
  222. if (rows < 1 || rows > 100) {
  223. printf("error: invalid value %u for argument rows.\n", rows);
  224. return EXIT_FAILURE;
  225. }
  226. if (cols < 1 || cols > 100) {
  227. printf("error: invalid value %u for argument cols.\n", cols);
  228. return EXIT_FAILURE;
  229. }
  230. term_init(&state.terminal, rows, cols, state.pango);
  231. signal(SIGCHLD, cmd_exit);
  232. LOG("Forking off command '%s'...", cmd);
  233. cmd_fd = cmd_open(cmd, &cmd_pid);
  234. if (-1 == cmd_fd) {
  235. LOG("Error running command.");
  236. return EXIT_FAILURE;
  237. }
  238. state.cmd_fd = cmd_fd;
  239. state.cmd = cmd;
  240. LOG("Process started with PID %u.", cmd_pid);
  241. state.cmd_pid = cmd_pid;
  242. notify_init("qcmd");
  243. NotifyNotification *not = notify_notification_new(cmd, " ", NULL);
  244. state.not = not;
  245. g_signal_connect(G_OBJECT(not),
  246. "closed",
  247. G_CALLBACK(not_closed_callback),
  248. NULL);
  249. notify_notification_set_timeout(not, 0);
  250. notify_notification_show(not, NULL);
  251. g_unix_fd_add(cmd_fd, G_IO_IN | G_IO_HUP | G_IO_ERR, cmd_read_callback, &state);
  252. state.loop = g_main_loop_new(NULL, FALSE);
  253. g_main_loop_run(state.loop);
  254. LOG("Main loop exited.");
  255. clean_up(&state);
  256. return EXIT_SUCCESS;
  257. }