#include #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "term.h" #define READ_BUF_SIZE 4096 #ifdef DEBUG #define LOG(args...) do {printf("[qcmd] "); printf(args); printf("\n");} while(0) #else #define LOG(args...) do {} while(0) #endif struct qcmd { GMainLoop *loop; NotifyNotification *not; int cmd_pid; int cmd_fd; char *cmd; int close_on_exit; struct term terminal; int pango; }; struct qcmd state = {0}; static void clean_up() { g_object_unref(G_OBJECT(state.not)); notify_uninit(); close(state.cmd_fd); } void cmd_exit() { LOG("cmd_exit()"); int status; pid_t pid = wait(&status); if (pid > 0) { if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { LOG("setting urgency to critical ..."); notify_notification_set_urgency(state.not, NOTIFY_URGENCY_CRITICAL); LOG("urgency set"); } } } if (state.close_on_exit) { notify_notification_close(state.not, NULL); } } static void exit_qcmd() { kill(state.cmd_pid, SIGKILL); cmd_exit(); g_main_loop_quit(state.loop); } static void not_closed_callback(gpointer data) { LOG("not_closed_callback()"); exit_qcmd(); } static gboolean cmd_read_callback(gint fd, GIOCondition condition, gpointer user_data) { char read_buf[READ_BUF_SIZE]; char *output; if (condition == G_IO_HUP) { return FALSE; } int size = 0; if ((size = read(fd, read_buf, READ_BUF_SIZE - 1)) > 0) { LOG("read %d bytes from pipe", size); read_buf[size] = '\0'; term_put_string(&state.terminal, read_buf); } output = term_to_string(&state.terminal); notify_notification_update(state.not, state.cmd, output, NULL); notify_notification_show(state.not, NULL); free(output); return TRUE; } static int cmd_open(char *cmd, int *pid) { int pt = posix_openpt(O_RDWR | O_NOCTTY); int pts_fl = fcntl(pt, F_GETFL); if (-1 == pts_fl) { exit(1); } if (-1 == fcntl(pt, F_SETFL, pts_fl | O_NONBLOCK)) { exit(1); } struct winsize winsz = { .ws_row = state.terminal.num_rows, .ws_col = state.terminal.num_cols, }; if (-1 == ioctl(pt, TIOCSWINSZ, &winsz)) { fprintf(stderr, "error: ioctl(): %s\n", strerror(errno)); exit(1); } int p = fork(); switch (p) { case -1: LOG("Error forking process."); exit(1); case 0: if (-1 == grantpt(pt)) { exit(1); } if (-1 == unlockpt(pt)) { exit(1); } char *pts_path = ptsname(pt); if (NULL == pts_path) { exit(1); } close(pt); if (-1 == setsid()) { fprintf(stderr, "error: setsid(): %s\n", strerror(errno)); exit(1); } LOG("Opening terminal slave '%s'", pts_path); int pts_fd = open(pts_path, O_RDWR); if (-1 == pts_fd) { exit(1); } if (-1 == ioctl(pts_fd, TIOCSCTTY, 0)) { fprintf(stderr, "error: ioctl(): %s\n", strerror(errno)); exit(1); } int pts_fl = fcntl(pts_fd, F_GETFL); if (-1 == pts_fl) { exit(1); } if (-1 == fcntl(pts_fd, F_SETFL, pts_fl | O_NONBLOCK)) { exit(1); } dup2(pts_fd, 0); dup2(pts_fd, 1); dup2(pts_fd, 2); close(pts_fd); execl("/bin/sh", "sh", "-c", cmd, (char *) NULL); exit(1); default: *pid = p; return pt; } } void print_version() { #ifdef VERSION printf("qcmd version %s\n", VERSION); #else printf("qcmd (unknown version)\n"); #endif } void print_help() { printf( "usage: qcmd [options] [command]\n" " -h, --help Display this help text.\n" " -v, --version Display version information.\n" " -r Number of rows in terminal output.\n" " -c Number of columns in terminal output.\n" " -C, --close-on-exit Close the notification on command exit.\n" " -p, --pango Output as pango markup.\n" ); } int main(int argc, char *argv[]) { char *cmd; int rows = 10; int cols = 80; int c; int option_index = 0; int cmd_pid; int cmd_fd; while (1) { static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, {"close-on-exit", no_argument, 0, 'C'}, {"pango", no_argument, 0, 'p'}, {0, 0, 0, 0}, }; c = getopt_long(argc, argv, "hvCpc:r:", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'h': print_help(); return EXIT_SUCCESS; case 'v': print_version(); return EXIT_SUCCESS; case 'C': state.close_on_exit = 1; break; case 'r': rows = atoi(optarg); break; case 'c': cols = atoi(optarg); break; case 'p': state.pango = 1; } } if (optind < argc) { cmd = argv[optind]; } if (cmd == NULL || strlen(cmd) == 0) { printf("error: no command specified.\n"); print_help(); return EXIT_FAILURE; } if (rows < 1 || rows > 100) { printf("error: invalid value %u for argument rows.\n", rows); return EXIT_FAILURE; } if (cols < 1 || cols > 100) { printf("error: invalid value %u for argument cols.\n", cols); return EXIT_FAILURE; } term_init(&state.terminal, rows, cols, state.pango); signal(SIGCHLD, cmd_exit); LOG("Forking off command '%s'...", cmd); cmd_fd = cmd_open(cmd, &cmd_pid); if (-1 == cmd_fd) { LOG("Error running command."); return EXIT_FAILURE; } state.cmd_fd = cmd_fd; state.cmd = cmd; LOG("Process started with PID %u.", cmd_pid); state.cmd_pid = cmd_pid; notify_init("qcmd"); NotifyNotification *not = notify_notification_new(cmd, " ", NULL); state.not = not; g_signal_connect(G_OBJECT(not), "closed", G_CALLBACK(not_closed_callback), NULL); notify_notification_set_timeout(not, 0); notify_notification_show(not, NULL); g_unix_fd_add(cmd_fd, G_IO_IN | G_IO_HUP | G_IO_ERR, cmd_read_callback, &state); state.loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(state.loop); LOG("Main loop exited."); clean_up(&state); return EXIT_SUCCESS; }