#include #include #include #include #include #include #include #include #include #include #include "bytenuts.h" #include "cheerios.h" #include "ingest.h" static void *ingest_thread(void *arg); static ingest_t ingest; static int mode_normal(int ch); static int mode_xmodem(int block_sz); static int handle_functions(int ch); static int inbuf_load(); static int print_stats(); static int auto_complete(); static int read_cmd_page(int idx); static void update_cmd_pg_status(); int ingest_start(bytenuts_t *bytenuts) { char *HOME; memset(&ingest, 0, sizeof(ingest)); ingest.input = bytenuts->in_win; ingest.term_lock = &bytenuts->term_lock; ingest.config = &bytenuts->config; ingest.cmd_pg_cur = -1; HOME = getenv("HOME"); if (HOME) { char *cwd = getcwd(NULL, 0); int idx = 0; chdir(HOME); while (!read_cmd_page(idx)) { idx++; } chdir(cwd); free(cwd); } if (ingest.cmd_pgs_n > 0) { ingest.cmd_pg_cur = 0; } update_cmd_pg_status(); ingest.running = 1; if (pthread_create(&ingest.thr, NULL, ingest_thread, NULL)) { return -1; } return 0; } int ingest_stop() { ingest.running = 0; pthread_join(ingest.thr, NULL); return 0; } static void * ingest_thread(void *arg) { int esc_flag; int ch; memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); ingest.inpos = 0; cheerios_insert( "Welcome to Bytenuts\r\n" "To quit press ctrl-b q\r\n" "To see all commands, press ctrl-b h\r\n", 82 ); bytenuts_set_status(STATUS_INGEST, "normal"); while (ingest.running) { esc_flag = 0; pthread_mutex_lock(ingest.term_lock); ch = wgetch(ingest.input); pthread_mutex_unlock(ingest.term_lock); if (ch == ERR) { nanosleep(&(struct timespec){ 0, 100000 }, NULL); continue; } if (ch == CTRL(ingest.config->escape)) { int should_quit = 0; int should_continue = 0; bytenuts_set_status(STATUS_INGEST, "control"); while (1) { pthread_mutex_lock(ingest.term_lock); ch = wgetch(ingest.input); pthread_mutex_unlock(ingest.term_lock); if (ch != ERR) break; nanosleep(&(struct timespec){ 0, 100000 }, NULL); } switch (ch) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': { bytenuts_set_status(STATUS_INGEST, "normal"); should_continue = 1; if (ingest.cmd_pgs_n == 0) break; int idx; char *toload; size_t toload_len; char **cmds = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds; int cmds_n = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds_n; if (ch == '0') ch += 10; idx = ch - '1'; if (idx >= cmds_n) break; toload = cmds[idx]; toload_len = strlen(toload); memcpy(ingest.inbuf, toload, toload_len); ingest.inpos = toload_len; ingest.inlen = toload_len; inbuf_load(); break; } case 'c': { bytenuts_set_status(STATUS_INGEST, "normal"); should_continue = 1; if (ingest.cmd_pgs_n == 0) { cheerios_insert("No quick commands\r\n", 19); break; } char **cmds = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds; int cmds_n = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds_n; char *cmd_str; size_t cmd_str_len; cmd_str_len = snprintf( NULL, 0, "Quick Commands (pg.%d of %d):\r\n", ingest.cmd_pg_cur + 1, ingest.cmd_pgs_n ); cmd_str = calloc(1, cmd_str_len + 1); sprintf( cmd_str, "Quick Commands (pg.%d of %d):\r\n", ingest.cmd_pg_cur + 1, ingest.cmd_pgs_n ); cheerios_insert(cmd_str, cmd_str_len); for (int i = 0; i < cmds_n; i++) { cmd_str_len = snprintf( NULL, 0, "%4d: %s\r\n", i+1, cmds[i] ); cmd_str = calloc(1, cmd_str_len + 1); sprintf(cmd_str, "%4d: %s\r\n", i+1, cmds[i]); cheerios_insert(cmd_str, cmd_str_len); free(cmd_str); } break; } case 'p': { bytenuts_set_status(STATUS_CMDPAGE, "selecting..."); while (1) { pthread_mutex_lock(ingest.term_lock); ch = wgetch(ingest.input); pthread_mutex_unlock(ingest.term_lock); if (ch != ERR) break; nanosleep(&(struct timespec){ 0, 100000 }, NULL); } should_continue = 1; bytenuts_set_status(STATUS_INGEST, "normal"); if (ch >= '1' || ch <= '9') { int pg = ch - '1'; if (pg >= 0 && pg < ingest.cmd_pgs_n) { ingest.cmd_pg_cur = pg; } } update_cmd_pg_status(); break; } case 'i': print_stats(); bytenuts_set_status(STATUS_INGEST, "normal"); should_continue = 1; break; case 'q': bytenuts_stop(); should_quit = 1; break; case 'x': ingest.mode = INGEST_MODE_XMODEM; bytenuts_set_status(STATUS_INGEST, "xmodem"); break; case 'X': ingest.mode = INGEST_MODE_XMODEM1K; bytenuts_set_status(STATUS_INGEST, "xmodem1k"); break; case 'h': cheerios_insert( "Commands (lead with ctrl-b):\r\n" " c: print available quick commands\r\n" " 0-9: load the given quick command (0 is 10)\r\n" " p: select a different quick commands page\r\n" " i: view info/stats\r\n" " x: start XModem upload with 128B payloads\r\n" " X: start XModem upload with 1024B payloads\r\n" " h: view this help\r\n" " q: quit Bytenuts\r\n", 313 ); bytenuts_set_status(STATUS_INGEST, "normal"); should_continue = 1; break; default: bytenuts_set_status(STATUS_INGEST, "normal"); should_continue = 1; break; } if (should_quit) { break; } else if (should_continue) { continue; } } switch (ingest.mode) { case INGEST_MODE_NORMAL: mode_normal(ch); break; case INGEST_MODE_XMODEM: mode_xmodem(128); break; case INGEST_MODE_XMODEM1K: mode_xmodem(1024); break; default: break; } sched_yield(); } pthread_exit(NULL); return NULL; } static int mode_normal(int ch) { if (handle_functions(ch)) return 0; switch (ch) { case '\n': /* send inputs */ if (cheerios_input(ingest.inbuf)) { break; } /* store in history */ ingest.history_pos = ingest.history_len; if (ingest.inlen > 0) { ingest.history_len++; ingest.history = realloc(ingest.history, ingest.history_len * sizeof(char *)); ingest.history[ingest.history_pos] = strdup(ingest.inbuf); ingest.history_pos++; } memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); ingest.inpos = 0; ingest.inlen = 0; inbuf_load(); break; case KEY_UP: /* load in history */ if (ingest.history_pos == 0) { /* top of history */ break; } else if (ingest.history_pos == ingest.history_len) { /* we have not loaded any history */ strcpy(ingest.tmp_history, ingest.inbuf); } ingest.history_pos--; memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); strcpy(ingest.inbuf, ingest.history[ingest.history_pos]); ingest.inlen = strlen(ingest.inbuf); ingest.inpos = ingest.inlen; inbuf_load(); break; case KEY_DOWN: /* load in history */ if (ingest.history_pos == ingest.history_len) { /* no history loaded */ break; } else if (ingest.history_pos == ingest.history_len - 1) { /* load back temp storage */ ingest.history_pos++; memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); strcpy(ingest.inbuf, ingest.tmp_history); ingest.inlen = strlen(ingest.inbuf); ingest.inpos = ingest.inlen; } else { ingest.history_pos++; memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); strcpy(ingest.inbuf, ingest.history[ingest.history_pos]); ingest.inlen = strlen(ingest.inbuf); ingest.inpos = ingest.inlen; } inbuf_load(); break; default: { char tmp[1024]; int tmp_len; if (ingest.inpos == sizeof(ingest.inbuf) - 1) break; tmp_len = ingest.inlen - ingest.inpos; memcpy(tmp, &ingest.inbuf[ingest.inpos], tmp_len); ingest.inbuf[ingest.inpos] = (char)ch; ingest.inpos++; ingest.inlen++; memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len); if (ingest.inlen == sizeof(ingest.inbuf)) { ingest.inbuf[ingest.inlen - 1] = '\0'; ingest.inlen--; } inbuf_load(); break; } } return 0; } static int mode_xmodem(int block_sz) { int quit_flag = 0; strcpy(ingest.tmp_history, ingest.inbuf); memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); ingest.inlen = 0; ingest.inpos = 0; if (ingest.prepend) free(ingest.prepend); ingest.prepend = strdup("Give me a path (ctrl-c to stop): "); inbuf_load(); while (ingest.running) { int ch; pthread_mutex_lock(ingest.term_lock); ch = wgetch(ingest.input); pthread_mutex_unlock(ingest.term_lock); if (ch == ERR || handle_functions(ch)) { nanosleep(&(struct timespec){ 0, 100000 }, NULL); continue; } switch (ch) { case '\n': pthread_mutex_lock(ingest.term_lock); werase(ingest.input); wmove(ingest.input, 0, 0); wprintw(ingest.input, "Sending..."); wrefresh(ingest.input); pthread_mutex_unlock(ingest.term_lock); cheerios_gofwd(-1); cheerios_xmodem(ingest.inbuf, block_sz); quit_flag = 1; break; case CTRL('c'): quit_flag = 1; break; case '\t': /* tab complete */ auto_complete(); inbuf_load(); break; default: { char tmp[1024]; int tmp_len; if (ingest.inpos == sizeof(ingest.inbuf) - 1) break; tmp_len = ingest.inlen - ingest.inpos; memcpy(tmp, &ingest.inbuf[ingest.inpos], tmp_len); ingest.inbuf[ingest.inpos] = (char)ch; ingest.inpos++; ingest.inlen++; memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len); if (ingest.inlen == sizeof(ingest.inbuf)) { ingest.inbuf[ingest.inlen - 1] = '\0'; ingest.inlen--; } inbuf_load(); break; } } if (quit_flag) { memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); strcpy(ingest.inbuf, ingest.tmp_history); ingest.inlen = strlen(ingest.inbuf); ingest.inpos = ingest.inlen; ingest.mode = INGEST_MODE_NORMAL; free(ingest.prepend); ingest.prepend = NULL; bytenuts_set_status(STATUS_INGEST, "normal"); inbuf_load(); return 0; } } return 0; } static int handle_functions(int ch) { // char st[128]; // sprintf(st, "%d", ch); // cheerios_info(st); switch (ch) { case KEY_PPAGE: /* page up */ cheerios_goback(cheerios_getmaxy() / 2); return 1; case KEY_NPAGE: /* page down */ cheerios_gofwd(cheerios_getmaxy() / 2); return 1; case KEY_SHOME: /* to back of log */ cheerios_goback(-1); return 1; case KEY_SEND: /* go to front of log */ cheerios_gofwd(-1); return 1; case 566: /* ctrl + up arrow */ cheerios_goback(1); return 1; case 525: /* ctrl + down arrow */ cheerios_gofwd(1); return 1; case KEY_RESIZE: bytenuts_update_screen_size(); return 1; case KEY_BACKSPACE: { if (ingest.inpos == 0) { return 1; } else if (ingest.inpos == ingest.inlen) { ingest.inpos--; ingest.inlen--; ingest.inbuf[ingest.inlen] = '\0'; inbuf_load(); return 1; } char tmp[1024]; int tmp_len = ingest.inlen - ingest.inpos; memcpy(tmp, &ingest.inbuf[ingest.inpos], ingest.inlen - ingest.inpos); ingest.inpos--; ingest.inlen--; memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len); ingest.inbuf[ingest.inlen] = '\0'; inbuf_load(); return 1; } case KEY_DC: /* delete key */ { char tmp[1024]; int tmp_len; if (ingest.inpos == ingest.inlen) { return 1; } else if (ingest.inpos == ingest.inlen - 1) { ingest.inbuf[ingest.inpos] = '\0'; ingest.inlen--; inbuf_load(); return 1; } tmp_len = ingest.inlen - ingest.inpos - 1; memcpy(tmp, &ingest.inbuf[ingest.inpos + 1], tmp_len); ingest.inlen--; memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len); ingest.inbuf[ingest.inlen] = '\0'; inbuf_load(); return 1; } case KEY_LEFT: if (ingest.inpos == 0) return 1; ingest.inpos--; inbuf_load(); return 1; case KEY_RIGHT: if (ingest.inpos == ingest.inlen) return 1; ingest.inpos++; inbuf_load(); return 1; case KEY_END: ingest.inpos = ingest.inlen; inbuf_load(); return 1; case KEY_HOME: ingest.inpos = 0; inbuf_load(); return 1; #if 0 case KEY_MOUSE: { MEVENT ev; if (getmouse(&ev) != OK) return 1; if (ev.bstate & BUTTON4_PRESSED) { /* scroll up */ cheerios_goback(1); } else if (ev.bstate & BUTTON5_PRESSED) { /* scroll down */ cheerios_gofwd(1); } return 1; } #endif default: return 0; } } static int inbuf_load() { int cx, cy = -1; int win_len = getmaxx(ingest.input) + 1; size_t prepend_len = ingest.prepend ? strlen(ingest.prepend) : 0; int buffer_len = win_len - prepend_len; int start = ingest.inlen > (win_len + prepend_len) ? (ingest.inlen - win_len - prepend_len - 1) : 0; pthread_mutex_lock(ingest.term_lock); werase(ingest.input); wmove(ingest.input, 0, 0); if (ingest.prepend) wprintw(ingest.input, "%s", ingest.prepend); if (start > 0) { waddch(ingest.input, '$'); start++; } if (start > ingest.inpos) { start = ingest.inpos; } else if ((start + buffer_len) < ingest.inpos) { start = ingest.inpos - buffer_len; } for (int i = start; i < ingest.inlen; i++) { if (i == ingest.inpos) getyx(ingest.input, cy, cx); waddch(ingest.input, ingest.inbuf[i]); } if (cx < 0) { cx = ingest.inlen; cy = 0; } wmove(ingest.input, cy, cx); wrefresh(ingest.input); pthread_mutex_unlock(ingest.term_lock); return 0; } static int print_stats() { char st_line[128]; sprintf(st_line, "\r\nSTATS\r\n"); cheerios_insert(st_line, strlen(st_line)); sprintf(st_line, "input line count: %d\r\n", ingest.history_len); cheerios_insert(st_line, strlen(st_line)); cheerios_print_stats(); bytenuts_print_stats(); return 0; } static int auto_complete() { char cmd[1024]; char line[1024]; char **matches = NULL; int nmatches = 0; FILE *sh = NULL; char *inbuf_cpy = NULL; char *inbuf_basename = NULL; size_t inbuf_basename_len; /* retrieve directory listing with ls */ inbuf_cpy = strdup(ingest.inbuf); if (ingest.inlen > 0 && ingest.inbuf[ingest.inlen - 1] == '/') { snprintf( cmd, sizeof(cmd), "cd %s 2> /dev/null && ls -1 2> /dev/null", ingest.inbuf ); inbuf_basename = ""; } else { snprintf( cmd, sizeof(cmd), "cd $(dirname \"%s\") 2> /dev/null && ls -1 2> /dev/null", ingest.inbuf ); inbuf_basename = basename(inbuf_cpy); } inbuf_basename_len = strlen(inbuf_basename); sh = popen(cmd, "r"); while (fgets(line, sizeof(line), sh)) { size_t line_len = strlen(line); while (line[line_len - 1] == '\n' || line[line_len - 1] == '\r') { line[line_len - 1] = '\0'; line_len = strlen(line); } if (!memcmp(inbuf_basename, line, inbuf_basename_len)) { nmatches++; matches = realloc(matches, sizeof(char *) * nmatches); matches[nmatches - 1] = strdup(line); } } /* the cd/ls just failed, ignore output */ int ret = pclose(sh); if (ret) goto auto_complete_cleanup; if (nmatches == 0) goto auto_complete_cleanup; /* check if we can fill the inbuf */ int offset = inbuf_basename_len; while (1) { char add = matches[0][offset]; for (int i = 1; i < nmatches; i++) { if (add != matches[i][offset]) { add = '\0'; break; } } if (add == '\0') break; offset++; } if (offset > inbuf_basename_len) { size_t add_len = offset - inbuf_basename_len; if (add_len + ingest.inlen >= sizeof(ingest.inbuf)) add_len = sizeof(ingest.inbuf) - ingest.inlen - 1; memcpy(&ingest.inbuf[ingest.inlen], &matches[0][inbuf_basename_len], add_len); ingest.inlen += add_len; ingest.inpos = ingest.inlen; } /* auto complete directory '/' */ if (nmatches == 1) { struct stat st; stat(ingest.inbuf, &st); if ( S_ISDIR(st.st_mode) && ingest.inlen < sizeof(ingest.inbuf) - 1 && (ingest.inpos == 0 || ingest.inbuf[ingest.inpos - 1] != '/') ) { ingest.inbuf[ingest.inpos] = '/'; ingest.inpos++; ingest.inlen++; } } auto_complete_cleanup: if (matches) { for (int i = 0; i < nmatches; i++) { free(matches[i]); } free(matches); } if (inbuf_cpy) free(inbuf_cpy); return 0; } static int read_cmd_page(int idx) { char fname[128]; FILE *fd; char line[1024]; snprintf(fname, sizeof(fname), ".config/bytenuts/commands%d", idx+1); fd = fopen(fname, "r"); if (!fd) return -1; ingest.cmd_pgs_n++; ingest.cmd_pgs = realloc( ingest.cmd_pgs, sizeof(*ingest.cmd_pgs) * ingest.cmd_pgs_n ); memset(&ingest.cmd_pgs[idx], 0, sizeof(*ingest.cmd_pgs)); while (fgets(line, sizeof(line), fd)) { size_t line_len = strlen(line); /* strip CRLF */ while (line_len > 0 && (line[line_len-1] == '\n' || line[line_len-1] == '\r')) { line_len--; line[line_len] = '\0'; } ingest.cmd_pgs[idx].cmds_n++; ingest.cmd_pgs[idx].cmds[ingest.cmd_pgs[idx].cmds_n-1] = strdup(line); } fclose(fd); return 0; } static void update_cmd_pg_status() { if (ingest.cmd_pgs_n > 0) { bytenuts_set_status(STATUS_CMDPAGE, "cmd pg.%d", ingest.cmd_pg_cur+1); } else { bytenuts_set_status(STATUS_CMDPAGE, "n/a"); } }