#include #include #include #include #include #include #include #include #include #include "term.h" #include "myc_mem.h" #include "myc_str.h" #include "sys_select.h" #include "uselect.h" #define ESC_TMO 1000 #define REF_TMO 50 #define HISTORY_LINES 256 #define L_ARROW 0200 #define R_ARROW 0201 #define U_ARROW 0202 #define D_ARROW 0203 #define EVT_CHAR '\0' #define DEL_CHAR '\b' #define RET_CHAR '\n' static char esc_key='\0'; static fd_set regMask; static int minReg=0, maxReg=0; static char filehead[256]=""; static char *func[256]={NULL}; void term_reg_socket(int socket) { FD_SET(socket, ®Mask); if (socket > maxReg) { if (maxReg == 0) minReg = socket; maxReg = socket; } else if (socket < minReg) { minReg = socket; } } void term_unr_socket(int socket) { FD_CLR(socket, ®Mask); } int term_raw_key(int *key, int msecTmo) { fd_set mask; mask=regMask; return(sys_select_or_key(&mask, msecTmo, key)); } int term_wait_socket(int socket, int msecTmo) { fd_set mask; struct timeval tmo; int i; if (socket == 0) { mask = regMask; } else { FD_ZERO(&mask); FD_SET(socket, &mask); } FD_SET(STDIN_FILENO, &mask); tmo.tv_sec=msecTmo / 1000; tmo.tv_usec=(msecTmo % 1000) * 1000; i=uselect(FD_SETSIZE, &mask, NULL, NULL, &tmo); if (FD_ISSET(STDIN_FILENO, &mask)) { return 0; } if (i<0) return i; if (i) { if (socket && FD_ISSET(socket, &mask)) { return socket; } for (i=minReg; i<=maxReg; i++) { if (FD_ISSET(i, &mask)) { return i; }; } return -1; } return 0; } int term_get_key(int *key, int msecTmo) { int iret; int k; int kode; fflush(stdout); if (esc_key==0) { iret=term_raw_key(&k, msecTmo); } else { k=esc_key; } while (k==27) { /* esc */ iret=term_raw_key(&k, ESC_TMO); switch (k) { case 'O': k=0217; break; /* ss3 */ case '[': k=0233; break; /* csi */ default: break; } } if (iret!=STDIN_FILENO) { esc_key=k; *key='\0'; return(iret); } switch (k) { case 0233: /* csi */ iret=term_raw_key(&k, ESC_TMO); kode=0; while (k>='0' && k <='9') { kode=kode*10+(k-'0'); iret=term_raw_key(&k, ESC_TMO); } if (iret!=STDIN_FILENO) { esc_key=k; *key='\0'; return(iret); } switch (k) { /* L,R,U,D arrows */ case 'D': k=L_ARROW; break; case 'C': k=R_ARROW; break; case 'A': k=U_ARROW; break; case 'B': k=D_ARROW; break; default: if (k=='~') { k = 128 + kode; } else { k += 128; } if (k>255) k='?'; } break; case 0217: /* ss3 */ iret=term_raw_key(&k, ESC_TMO); if (iret!=STDIN_FILENO) { esc_key=k; *key='\0'; return(iret); } if (k=='M') { /* enter */ k=RET_CHAR; } else if (k >= 'l' && k <= 'y') { k=k-64; } else { switch (k) { /* L,R,U,D arrows */ case 'D': k=L_ARROW; break; case 'C': k=R_ARROW; break; case 'A': k=U_ARROW; break; case 'B': k=D_ARROW; break; default: k += 128; if (k>255) k='?'; } } break; case 0177: /* del */ case '\b': /* bs */ k=DEL_CHAR; break; case '\r': /* cr */ case '\n': /* lf */ k=RET_CHAR; break; case 4: /* ctrl-D, end of file */ break; case EVT_CHAR: /* time out*/ break; default: if (k>0176) { k='?'; } } *key=k; esc_key='\0'; return (STDIN_FILENO); } static char *history[HISTORY_LINES]={NULL}; /* history array: it's a cyclic buffer, when it is full, the oldest values are overwritten. */ static int hist_pos=0; /* position when scrolling through the history */ static int hist_end=0; /* end of history. Always history[hist_end]==NULL */ FILE *term_open_pref(int temp, char *head, char *mode) { char buf[PATH_MAX+1], old[PATH_MAX+1], wd[PATH_MAX]; char *usr, *home, *p; int l; FILE *fil; mode_t oldmask; usr = getenv("USER"); if (temp && usr==NULL) return NULL; if (usr != NULL && *usr != '\0' && strstr(usr, "lnsg") != 0) { if (! getcwd(wd, sizeof wd)) return NULL; home=getenv("HOME"); if (!home) return NULL; realpath(home, buf); l = strlen(buf); buf[l] = '/'; l++; buf[l]= '\0'; if (wd[l] != '\0' && strncmp(wd, buf, l) == 0) { /* wd starts with $HOME */ p = strchr(wd+l, '/'); if (p) *p='\0'; /* determine 1st subdirectory of $HOME */ if (temp) { str_append(wd, "_lnsg"); usr = wd+l; } else { home = wd; } } else { home = buf; } } else { home=getenv("HOME"); if (!home) return NULL; } if (temp) { /* old file name */ str_copy(old, "/tmp/"); str_append(old, head); str_append(old, "."); str_append(old, usr); /* new file name */ str_copy(buf, "/tmp/six/"); oldmask = umask(0); mkdir(buf, S_IRWXU+S_IRWXG+S_IRWXO); umask(oldmask); str_append(buf, head); str_append(buf, "."); str_append(buf, usr); /* usr is the username, or lnsg_ */ } else { /* old file name */ str_copy(old, home); str_append(old, "/."); str_append(old, head); /* new file name */ str_copy(buf, home); str_append(buf, "/.six/"); mkdir(buf, S_IRWXU+S_IRGRP+S_IXGRP+S_IROTH+S_IXOTH); str_append(buf, head); } rename(old, buf); /* if old file exists, rename it to new */ if (*mode == 'd') { unlink(buf); return NULL; } else { fil = fopen(buf, mode); return fil; } } char *term_fgets(char *buf, int size, FILE *fil) { char *p, *ret; char skipbuf[256]; buf[0]='\0'; ret=fgets(buf, size, fil); if (ret==NULL) { return NULL; } p=strchr(buf,'\n'); if (p==NULL) { while (p==NULL) { /* skip rest of line */ ret=fgets(skipbuf, sizeof(skipbuf), fil); if (ret==NULL) { return NULL; } p=strchr(skipbuf,'\n'); } } else { *p='\0'; } return ret; } void term_save_hist(int trimlast) { FILE *fil; int i,n; if (filehead[0]=='\0') return; fil=term_open_pref(1, filehead, "w"); if (fil==NULL) return; n=HISTORY_LINES-1; if (trimlast) { n--; } i=hist_end; while (n>0) { n--; i++; if (i>=HISTORY_LINES) i=0; if (history[i]!=NULL) { fputs(history[i], fil); fputs("\n", fil); } } fclose(fil); } void term_read_hist(char *id, char *instr) { FILE *fil; int i; char buf[1024], *lin; str_copy(filehead, id); str_append(filehead, "_hist_"); str_append(filehead, instr); fil=term_open_pref(1, filehead, "r"); if (fil==NULL) return; hist_end=0; while (hist_end0) { lin=MALLOC(i+1); strncpy(lin, buf, i+1); history[hist_end]=lin; hist_end++; } } fclose(fil); history[hist_end]=NULL; hist_pos=hist_end; } static int dirty=0; /* line is to be cleared through a call of term_clear */ void term_clear(void) { if (dirty) { fputs("\r\033[K\033[0m", stdout); /* clear to end of line, clear colors */ dirty=0; } } void term_off(void) { dirty=1; term_clear(); sys_keys_off(); } int term_get_line(char *buf, int size, int *pos, char *prompt, fd_set *mask) { char *lin; int key; int i,l,iret,buflen; char tmp[1024]; static char back[512]=""; if (back[0] == '\0') { memset(back, '\b', sizeof back); } buf[size-1]='\0'; /* make sure buf is null terminated */ l=strlen(buf); if (*pos>l) { *pos=l; } else if (*pos<0) { *pos=0; } iret=term_get_key(&key, 0); while (1) { if (iret==-1 || key == RET_CHAR || key==EVT_CHAR) { /* refresh after a short timeout */ snprintf(tmp, sizeof tmp, "%s%s%s%s%s%.*s", "\r\033[0m\033[1m", /* no color, bold */ prompt, "\033[34m", /* blue */ buf, "\033[K\033[0m", /* clear to end of line, clear colors */ l - *pos, back); fputs(tmp, stdout); if (iret==-1) { iret=term_get_key(&key, -1); /* no timeout */ } } switch (key) { case EVT_CHAR: /* caller must clear line before next write with term_clear() */ dirty=1; return(iret); /* interrupted EXIT */ case RET_CHAR: i=hist_end-1; if (i<0) i=HISTORY_LINES-1; if ((history[i]==NULL || 0!=strcmp(history[i], buf)) && buf[0]!='\0') { /* do not save equal and empty lines */ buflen=strlen(buf)+1; lin=MALLOC(buflen); strncpy(lin, buf, buflen); history[hist_end]=lin; hist_end++; if (hist_end>=HISTORY_LINES) hist_end=0; if (history[hist_end]!=NULL) { FREE(history[hist_end]); /* clear line at end of history */ } history[hist_end] = NULL; } hist_pos=hist_end; term_save_hist(0); return(STDIN_FILENO); /* normal EXIT */ case DEL_CHAR: if (*pos>0) { for (i=*pos; buf[i]!='\0'; i++) { buf[i-1] = buf [i]; } buf[i-1]='\0'; (*pos)--; l--; } break; case L_ARROW: if (*pos>0) (*pos)--; break; case R_ARROW: if (buf[*pos]!='\0') (*pos)++; break; case U_ARROW: case D_ARROW: if (key==U_ARROW) { i=hist_pos-1; if (i<0) i=HISTORY_LINES-1; } else { i=hist_pos+1; if (i>=HISTORY_LINES) i=0; } if (history[i]!=NULL) { strncpy(buf, history[i], size-1); buf[size-1]='\0'; hist_pos=i; l=strlen(buf); } else { buf[0]='\0'; l=0; if (history[hist_pos]!=NULL) { hist_pos=i; } } *pos=l; break; case 1: /* ctrl-A: jump to begin of line */ *pos = 0; break; case 5: /* ctrl-E: jump to end of line */ *pos = strlen(buf); break; case 4: /* ctrl-D: abort */ return -2; default: if (key <' ' || key > 0176) { /* untreated special key */ if (func[key] != NULL) { snprintf(buf, size, "%s", func[key]); key = RET_CHAR; continue; } key = '?'; } if (l*pos; i--) { buf[i]=buf[i-1]; } (*pos)++; l++; buf[i]=key; buf[l]='\0'; } break; } iret=term_get_key(&key, REF_TMO); } } void term_define_key(char *cmd, int keyArg) { int iret; int key; if (keyArg) { key = keyArg; } else { if (*cmd) { printf("\n# press key for command '%s' or return", cmd); } else { printf("\n# press key for which to remove command"); } iret = term_get_key(&key, 5000); } switch (key) { case EVT_CHAR: case DEL_CHAR: case L_ARROW: case R_ARROW: case U_ARROW: case D_ARROW: break; case RET_CHAR: printf("\n# canceled\n"); default: if (key <' ' || key > 0176) { /* special key */ if (*cmd) { if (func[key]) free(func[key]); func[key] = strdup(cmd); if (!keyArg) { printf("\n# programmed key %d\n", key); } } else { if (func[key]) { if (!keyArg) { printf("\n# removed command '%s' from key %d\n", func[key], key); } free(func[key]); } else { if (!keyArg) { printf("\n# no command on key %d\n", key); } } func[key] = NULL; } return; } } printf("\n# key %d is not programmable\n", key); } void term_save_keys(FILE *fil, void (*out)(char *)) { int key; char buf[128]; for (key=0; key<256; key++) { if (func[key]) { fprintf(fil, "%d %s\n", key, func[key]); if (out) { snprintf(buf, sizeof buf, " key %d defined as '%s'\n", key, func[key]); out(buf); } } } } void term_load_keys(FILE *fil, void (*out)(char *)) { char line[136], buf[128]; int key, l; while (term_fgets(line, sizeof line, fil)) { key = 0; sscanf(line, "%d %n", &key, &l); if (key != 0) { term_define_key(line+l, key); if (out) { snprintf(buf, sizeof buf, " key %d defined as '%s'\n", key, line+l); out(buf); } } } }