/* * Copyright (c) 2022 Marcus Glocker * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * ChangeLog * --------- * 07.04.2022: v1.0 */ #include #include #include #include #include #include #include #include #include #include #include /* * Marcos. */ #define MAX_DIGITS 3 /* Maximum digits to enter. */ #define WAIT_INPUT 3 * 1000 /* Input wait time in ms. */ #define PLAYER_BIN "/usr/local/bin/mplayer" /* Video player. */ #define PIDFILE "movie_select.pid" /* PID file. */ #define LOGFILE "movie_select.log" /* LOG file. */ /* * Global variables. */ int quit = 0; char movies_path[128]; /* * Function proto-types. */ void signal_handler(const int); void logx(FILE *, const char *, ...); char *senv(char *); char *find_movie(char *); int play_movie(char *); void usage(void); /* * Functions. */ void signal_handler(const int sig) { switch (sig) { case SIGINT: case SIGTERM: quit = 1; break; default: break; } } void logx(FILE *logfile, const char *log_string, ...) { time_t t; struct tm *tm; va_list ap; /* Timestamp. */ (void)time(&t); tm = localtime(&t); fprintf(logfile, "%02d.%02d.%04d, %02d:%02d:%02d: ", tm->tm_mday, tm->tm_mon, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec); /* Log string. */ va_start(ap, log_string); vfprintf(logfile, log_string, ap); va_end(ap); /* Flush. */ fprintf(logfile, "\n"); fflush(logfile); } char * senv(char *env_var) { char env[128]; snprintf(env, sizeof(env), "%s=%s", env_var, getenv(env_var)); return strdup(env); } char * find_movie(char *digit) { DIR *odir; struct dirent *rdir; char filenum[MAX_DIGITS+2]; char movie[128]; memset(filenum, 0, sizeof(filenum)); snprintf(filenum, sizeof(filenum), "%s_", digit); odir = opendir(movies_path); if (odir == NULL) return NULL; while ((rdir = readdir(odir)) != NULL) { if (rdir->d_name[0] == '.') continue; if (strncmp(filenum, rdir->d_name, strlen(filenum)) == 0) { snprintf(movie, sizeof(movie), "%s%s", movies_path, rdir->d_name); return strdup(movie); } } return NULL; } int play_movie(char *movie) { pid_t pid; int status; char *argv[4], *envp[11]; pid = fork(); if (pid == -1) return -1; /* Child process to run player. */ if (pid == 0) { /* Send stdout, stdin, and stderr to /dev/null. */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); open("/dev/null", O_RDWR); dup(STDIN_FILENO); dup(STDIN_FILENO); /* Execute movie player. */ argv[0] = strdup("mplayer"); argv[1] = strdup("-fs"); argv[2] = strdup(movie); argv[3] = NULL; envp[0] = senv("XTERM_SHELL"); envp[1] = senv("PWD"); envp[2] = senv("HOME"); envp[3] = senv("OLDPWD"); envp[4] = senv("DISPLAY"); envp[5] = senv("PATH"); envp[6] = senv("TERM"); envp[7] = senv("SHELL"); envp[8] = senv("USER"); envp[9] = senv("XTERM_LOCALE"); envp[10] = NULL; if (execve(PLAYER_BIN, argv, envp) == -1) return -1; } /* Parent process waits for child to exit. */ while (wait(&status) > 0); return 0; } void usage(void) { printf("movie_select [movies directory]\n"); exit(1); } /* * Main program. */ int main(int argc, char **argv) { WINDOW *win; int x, y, c, i, nfds; char digit[MAX_DIGITS+1]; char *movie; struct stat sb; struct pollfd pfd[1]; FILE *logfile, *pidfile; /* We expect movie path as argument. */ if (argc < 2) usage(); /* Setup signal handler. */ signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); /* Normalize movie path. */ memset(movies_path, 0, sizeof(movies_path)); strlcpy(movies_path, argv[1], sizeof(movies_path)); if (movies_path[strlen(movies_path)-1] != '/') snprintf(movies_path, sizeof(movies_path), "%s/", movies_path); if (stat(movies_path, &sb) != 0) err(1, "%s", movies_path); /* Create PID file. */ pidfile = fopen(PIDFILE, "w"); if (pidfile == NULL) err(1, "fopen pid"); fprintf(pidfile, "%d\n", getpid()); fclose(pidfile); /* Open log file. */ logfile = fopen(LOGFILE, "a+"); if (logfile == NULL) err(1, "fopen log"); logx(logfile, "movie_select startup"); logx(logfile, "movies path = %s", movies_path); /* Give xterm time to settle it's geometry. */ sleep(1); /* Initialize curses. */ initscr(); clear(); noecho(); curs_set(0); refresh(); win = newwin(0, 0, 0, 0); y = win->_maxy / 2; x = win->_maxx / 2; logx(logfile, "y_orig = %d, x_orig = %d", win->_maxy, win->_maxx); logx(logfile, "y_center = %d, x_center = %d", y, x); i = 0; memset(digit, 0, sizeof(digit)); while (!quit) { /* Get character. */ pfd[0].fd = 1; pfd[0].events = POLLIN; nfds = poll(pfd, 1, WAIT_INPUT); if (nfds > 0) c = getch(); else if (nfds == 0) { if (digit[0] == 0) continue; /* Input timeout reached, search for movie. */ logx(logfile, "Digit entered = '%s'", digit); movie = find_movie(digit); if (movie != NULL) { logx(logfile, "Movie #%s found, playing %s", digit, movie); wclear(win); wrefresh(win); play_movie(movie); logx(logfile, "Movie #%s playing completed", digit); } else { wclear(win); wrefresh(win); logx(logfile, "Movie #%s not found, skipping", digit, movie); } i = 0; memset(digit, 0, sizeof(digit)); continue; } else if (nfds == -1) continue; /* Only accept digits. */ if (c < 48 || c > 57) continue; /* Check if maximum digits were exceeded. */ if (i == MAX_DIGITS) { i = 0; memset(digit, 0, sizeof(digit)); wclear(win); wrefresh(win); continue; } /* Display digit. */ digit[i++] = c; mvwprintw(win, y, x - (i / 2), "%s", digit); wrefresh(win); } /* Shutdown. */ delwin(win); endwin(); logx(logfile, "movie_select shutdown"); fclose(logfile); return 0; }