diff --git a/.gitignore b/.gitignore index 586b3f8c..191abd6a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,14 @@ build_* # gutentags tags + # subprojects subprojects/radio_tool subprojects/codec2 + +#ignore log files +*.log + +#ignore linux openrtx emulator history file +.emulatorsh_history + diff --git a/meson.build b/meson.build index 6675bfa3..751f7483 100644 --- a/meson.build +++ b/meson.build @@ -309,7 +309,7 @@ dm1801_def = def + mk22fn512_def + {'PLATFORM_DM1801': ''} linux_c_args = ['-DPLATFORM_LINUX'] linux_cpp_args = ['-std=c++14', '-DPLATFORM_LINUX'] -linux_l_args = ['-lm'] +linux_l_args = ['-lm', '-lreadline'] # Add AddressSanitizer if required if get_option('asan') diff --git a/meta/leak_check.txt b/meta/leak_check.txt new file mode 100644 index 00000000..3cf87f2b --- /dev/null +++ b/meta/leak_check.txt @@ -0,0 +1,124 @@ +p +p +p +p +r 12 +key 1 2 3 4 5 6 enter +key esc +key 1 2 3 4 5 6 enter +key 4 5 1 8 enter +key 4 5 1 8 enter +key 4 6 9 enter +key esc +key enter +key down down down +key down +key enter +key esc +key down down down down down +key enter +key esc +p +p +key enter +key esc +key 4 5 1 8 enter +key 1 4 6 5 2 enter +p +p +key enter down down down down down enter +key esc +key down down down down +key enter +key esc +key esc +key enter up up enter +key up +key esc +key esc +r 12 +r 15 +s +vb 7.6 +key enter +key esc +s +channel 1 +channel 2 +ch 3 +ch 5 +ch 15 +key esc +ch 2 +key esc +key esc +key esc +key enter +key esc +key esc +key enter +key down down down +key enter +key enter +key down +key enter +key up +key up +key up +key up +key up +key up +key down +key down +key esc + +p +p +key esc +key esc +key esc +key up +key esc +key up +key up +key 4 5 1 8 enter enter +p +p +key 1 4 6 5 2 enter 4 5 1 8 enter +p +p +p +p +key enter up up enter +key up +key esc +key down down down down enter +key esc +key down down down down down enter +key esc +key esc + +key esc +key esc +key esc +key up +key esc +key up +key up +key 4 5 1 8 enter enter +p +p +key 1 4 6 5 2 enter 4 5 1 8 enter +ready +p +p +p +p +key enter up up enter +key up +key esc +key down down down down enter +key esc +key down down down down down enter +key esc +key esc diff --git a/meta/menu_test.txt b/meta/menu_test.txt new file mode 100644 index 00000000..f66fe5c9 --- /dev/null +++ b/meta/menu_test.txt @@ -0,0 +1,9 @@ +sleep 4000 +nop SAT menu +key enter DOWN down down down down enter +ready +screenshot sat.bmp +nop GPS menu +key esc DOWN down down down enter +ready +screenshot gps.bmp diff --git a/meta/ptt_test.txt b/meta/ptt_test.txt new file mode 100644 index 00000000..7c3f2cf3 --- /dev/null +++ b/meta/ptt_test.txt @@ -0,0 +1,6 @@ +sleep 3000 +screenshot noptt.bmp +key 1 2 3 +ptt +sleep 1000 +screenshot ptt.bmp diff --git a/meta/readme b/meta/readme new file mode 100644 index 00000000..bd7a79bf --- /dev/null +++ b/meta/readme @@ -0,0 +1,16 @@ + + + +uidev: + meson compile -C build_linux openrtx_linux + cat sat_menu_dev.txt | build_linux/openrtx_linux + convert SAT.bmp -resize 600% SAT.jpg + + +valgrind: + meson compile -C build_linux openrtx_linux + cat leak_check.txt | valgrind --leak-check=full --log-file=valgrind.log build_linux/openrtx_linux +record: + meson compile -C build_linux openrtx_linux + cat record_demo.txt | build_linux/openrtx_linux + diff --git a/meta/sat_menu_dev.txt b/meta/sat_menu_dev.txt new file mode 100644 index 00000000..ca16ec02 --- /dev/null +++ b/meta/sat_menu_dev.txt @@ -0,0 +1,3 @@ +key enter down down down down down enter +sleep 5000 +screenshot SAT.bmp diff --git a/meta/select_menu.txt b/meta/select_menu.txt new file mode 100644 index 00000000..2119f551 --- /dev/null +++ b/meta/select_menu.txt @@ -0,0 +1,9 @@ +key enter down down down down down enter +sleep 1000 +key down enter +sleep 5000 +nop sleep 1000 +nop key enter +nop sleep 1000 +nop screenshot delta.bmp + diff --git a/platform/drivers/display/display_libSDL.c b/platform/drivers/display/display_libSDL.c index ec262e00..33b1d949 100644 --- a/platform/drivers/display/display_libSDL.c +++ b/platform/drivers/display/display_libSDL.c @@ -57,6 +57,100 @@ SDL_Texture *displayTexture; /* SDL rendering surface */ void *frameBuffer; /* Pointer to framebuffer */ bool inProgress; /* Flag to signal when rendering is in progress */ + +int screenshot_display(const char *filename) +{ + //https://stackoverflow.com/a/48176678 + //user1902824 + //modified to keep renderer and display texture references in the body rather than as a parameter + SDL_Renderer * ren = renderer; + SDL_Texture * tex = displayTexture; + int err = 0; + + + SDL_Texture *ren_tex; + SDL_Surface *surf; + int st; + int w; + int h; + int format; + void *pixels; + + pixels = NULL; + surf = NULL; + ren_tex = NULL; + format = SDL_PIXELFORMAT_RGBA32; + + /* Get information about texture we want to save */ + st = SDL_QueryTexture(tex, NULL, NULL, &w, &h); + if (st != 0) { + SDL_Log("Failed querying texture: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + + ren_tex = SDL_CreateTexture(ren, format, SDL_TEXTUREACCESS_TARGET, w, h); + if (!ren_tex) { + SDL_Log("Failed creating render texture: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + + /* + * Initialize our canvas, then copy texture to a target whose pixel data we + * can access + */ + st = SDL_SetRenderTarget(ren, ren_tex); + if (st != 0) { + SDL_Log("Failed setting render target: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0x00); + SDL_RenderClear(ren); + st = SDL_RenderCopy(ren, tex, NULL, NULL); + if (st != 0) { + SDL_Log("Failed copying texture data: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + /* Create buffer to hold texture data and load it */ + pixels = malloc(w * h * SDL_BYTESPERPIXEL(format)); + if (!pixels) { + SDL_Log("Failed allocating memory\n"); + err++; + goto cleanup; + } + st = SDL_RenderReadPixels(ren, NULL, format, pixels, w * SDL_BYTESPERPIXEL(format)); + if (st != 0) { + SDL_Log("Failed reading pixel data: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + /* Copy pixel data over to surface */ + surf = SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, SDL_BITSPERPIXEL(format), w * SDL_BYTESPERPIXEL(format), format); + if (!surf) { + SDL_Log("Failed creating new surface: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + /* Save result to an image */ + st = SDL_SaveBMP(surf, filename); + if (st != 0) { + SDL_Log("Failed saving image: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + SDL_Log("Saved texture as BMP to \"%s\"\n", filename); + +cleanup: + SDL_FreeSurface(surf); + free(pixels); + SDL_DestroyTexture(ren_tex); + return err; +} + + /** * @internal * Internal helper function which fetches pixel at position (x, y) from framebuffer @@ -105,7 +199,8 @@ void display_init() SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3, - SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); + SDL_WINDOW_SHOWN ); + //removed RESIZABLE flag so automatic screen recording is a little easier renderer = SDL_CreateRenderer(window, -1, 0); SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT); diff --git a/platform/drivers/keyboard/keyboard_linux.c b/platform/drivers/keyboard/keyboard_linux.c index c67c9366..ea27557b 100644 --- a/platform/drivers/keyboard/keyboard_linux.c +++ b/platform/drivers/keyboard/keyboard_linux.c @@ -23,13 +23,17 @@ #include #include +extern keyboard_t shellkeyq_get(); + void kbd_init() { } - keyboard_t kbd_getKeys() { keyboard_t keys = 0; SDL_PumpEvents(); + + //this pulls in emulated keypresses from the command shell + keys |= shellkeyq_get(); const uint8_t *state = SDL_GetKeyboardState(NULL); if (state[SDL_SCANCODE_0]) keys |= KEY_0; diff --git a/platform/targets/linux/emulator/emulator.c b/platform/targets/linux/emulator/emulator.c index dc5d8811..30122b8f 100644 --- a/platform/targets/linux/emulator/emulator.c +++ b/platform/targets/linux/emulator/emulator.c @@ -20,42 +20,261 @@ #include "emulator.h" + #include #include #include #include +#include +#include + +#include +#include + radio_state Radio_State = {12, 8.2f, 3, 4, 1, false}; -int CLIMenu() -{ - int choice = 0; - printf("Select the value to change:\n"); - printf("1 -> RSSI\n"); - printf("2 -> Vbat\n"); - printf("3 -> Mic Level\n"); - printf("4 -> Volume Level\n"); - printf("5 -> Channel selector\n"); - printf("6 -> Toggle PTT\n"); - printf("7 -> Print current state\n"); - printf("8 -> Exit\n"); - printf("> "); - do - { - scanf("%d", &choice); - } while (choice < 1 || choice > 8); - printf("\033[1;1H\033[2J"); - return choice; +extern int screenshot_display(const char *filename); + +typedef int (*_climenu_fn)(void* self, int argc, char ** argv ); + +typedef struct { + char * name; + char * description; + void * var; + _climenu_fn fn; +} _climenu_option; + +enum shell_retvals { + SH_ERR=-1, + SH_CONTINUE=0, + SH_WHAT=1, + SH_EXIT_OK=2, +}; + + + + + + +keyboard_t _shellkeyq[25] = {0}; +int _skq_cap = 25; +int _skq_head; +int _skq_tail; +int _skq_in; +int _skq_out; +void _dump_skq(){ + for( int i = 0; i < _skq_cap; i++){ + printf("skq[%d] == %d\n", i, _shellkeyq[i]); + } +} +void shellkeyq_put(keyboard_t keys){ + //note - we must allow keys == 0 to be inserted because otherwise a queue full of + // [1,1,1,1,1] is simulating HOLDING 1, and we sometimes (well, often) want + // [1,0,1,0,1,0] to simulate separate keypresses + // this, of course, relies on the kbd_thread getting just one element off the queue + // for every kbd_getKeys(). + if( _skq_in > _skq_out + _skq_cap ){ + printf("too many keys!\n"); + return; + } + _shellkeyq[ _skq_tail ] = keys; + _skq_in++; + _skq_tail = (_skq_tail + 1 ) % _skq_cap; + /*printf("head: %d tail: %d in %d out %d\n", _skq_head, _skq_tail, _skq_in, _skq_out);*/ +} +keyboard_t shellkeyq_get(){ + if( _skq_in > _skq_out ){ + //only if we've fallen behind and there's data in there: + keyboard_t out = _shellkeyq[ _skq_head ]; + _shellkeyq[ _skq_head ] = 0; + _skq_out++; + _skq_head = (_skq_head + 1 ) % _skq_cap; + /*printf("head: %d tail: %d in %d out %d\n", _skq_head, _skq_tail, _skq_in, _skq_out);*/ + /*_dump_skq();*/ + return out; + } else { + return 0; //no keys + } +} +void _test_skq(){ + for(int i = 0; i < 257; i++){ + shellkeyq_put(i+1); + } + + //clear it out now + while( shellkeyq_get() ); +} +int shell_ready( + __attribute__((unused)) void * _self, + __attribute__((unused)) int _argc, + __attribute__((unused)) char ** _argv ){ + while( _skq_in > _skq_out ){ + usleep(10*1000); //sleep until keyboard is caught up + } + return SH_CONTINUE; } -void updateValue(float *curr_value) -{ - printf("Current value: %f\n", *curr_value); - printf("New value: \n"); - scanf("%f", curr_value); +keyboard_t keyname2keyboard(char * name){ + /*The line noise at the end of this comment is a vim macro for taking the keyboard.h + interface and putting it into the format further below + You can load it into vim register k with "kyy + and run the macro with @k (and then you can repeat a macro register application with @@ ) + (substitute k with any register you like) + Once you've got all the names quoted, you can J them all together into a nice block. + + _i"ElC", + + */ + char * names[] = { + "KEY_0", "KEY_1", "KEY_2", "KEY_3", "KEY_4", "KEY_5", "KEY_6", "KEY_7", + "KEY_8", "KEY_9", "KEY_STAR", "KEY_HASH", "KEY_ENTER", "KEY_ESC", "KEY_UP", + "KEY_DOWN", "KEY_LEFT", "KEY_RIGHT", "KEY_MONI", "KEY_F1", "KEY_F2", "KEY_F3", + "KEY_F4", "KEY_F5", "KEY_F6", "KEY_F7", "KEY_F8", "KEY_F9", "KEY_F10", + }; + int numnames = sizeof(names)/sizeof(char*); + for( int i = 0; i < numnames; i++ ){ + if( strcasecmp(name,names[i]+4) == 0 ){ //notice case insensitive + /*printf("MATCH with %s\n", names[i]);*/ + //+4 to skip the KEY_ on all the names + //so if name == "2", this whole function will return equivalent to KEY_2 cpp define + //and if name=="LEFT", then you get equivalent to KEY_LEFT cpp define + return (1 << i); + //order matters a great deal in names array, has to match + //the bit field generated in interface/keyboard.h + } + } + return 0; } -void printState() +int pressKey( __attribute__((unused)) void * _self, int _argc, char ** _argv ){ + //press a couple keys in sequence + /*_climenu_option * self = (_climenu_option*) _self;*/ + printf("Press Keys: [\n"); + keyboard_t last = 0; + for( int i = 0; i < _argc; i++ ){ + if( _argv[i] != NULL ){ + printf("\t%s, \n", _argv[i]); + keyboard_t press = keyname2keyboard( _argv[i] ); + if( press == last ){ + //otherwise if you send key ENTER DOWN DOWN DOWN DOWN DOWN + //it will just hold DOWN for (5/(kbd_task_hz)) seconds + //so we need to give it a 0 value to get a 'release' + //so the next input is recognized as separate + //we only need to do this if we have two identical keys back to back, + //because keyboard_t will have a zero for this key's + //flag on other keys, which gives us the release we need + shellkeyq_put( 0 ); + } + shellkeyq_put(press); + last = press; + } + } + printf("\t]\n"); + shell_ready(NULL,0,NULL); + return SH_CONTINUE; // continue +} +int pressMultiKeys( __attribute__((unused)) void * _self, int _argc, char ** _argv ){ +//pressMultiKeys allows for key combos by sending all the keys specified in one keyboard_t + /*_climenu_option * self = (_climenu_option*) _self;*/ + printf("Press Keys: [\n"); + keyboard_t combo = 0; + for( int i = 0; i < _argc; i++ ){ + if( _argv[i] != NULL ){ + printf("\t%s, \n", _argv[i]); + combo |= keyname2keyboard( _argv[i] ); + } + } + shellkeyq_put( combo ); + printf("\t]\n"); + shell_ready(NULL,0,NULL); + return SH_CONTINUE; // continue +} +//need another function to press them in sequence by loading up a queue for keypress_from_shell to pull from + + + + +int template(void * _self, int _argc, char ** _argv ){ + _climenu_option * self = (_climenu_option*) _self; + printf( "%s\n\t%s\n" , self->name, self->description); + + for( int i = 0; i < _argc; i++ ){ + if( _argv[i] != NULL ){ + printf("\tArgs:\t%s\n", _argv[i]); + } + } + return SH_CONTINUE; // continue +} + +int screenshot(__attribute__((unused)) void * _self, int _argc, char ** _argv ){ + char * filename = "screenshot.bmp"; + if( _argc && _argv[0] != NULL ){ + filename = _argv[0]; + } + return screenshot_display(filename) == 0 ? SH_CONTINUE : SH_ERR; + //screenshot_display returns 0 if ok, which is same as SH_CONTINUE +} +/* +int record_start(__attribute__((unused)) void * _self, int _argc, char ** _argv ){ + char * filename = "screen.mkv"; + if( _argc && _argv[0] != NULL ){ + filename = _argv[0]; + } + //id="xwininfo -name 'OpenRTX' | grep id: |cut -d ' ' -f 4"; + //system("ffmpeg -f x11grab -show_region 1 -region_border 10 -window_id 0x2600016 -i :0.0 out.mkv"); + //https://stackoverflow.com/questions/14764873/how-do-i-detect-when-the-contents-of-an-x11-window-have-changed + return SH_ERR; +} +int record_stop( + __attribute__((unused)) void * _self, + __attribute__((unused)) int _argc, + __attribute__((unused)) char ** _argv ){ + return SH_ERR; +} +*/ +int setFloat(void * _self, int _argc, char ** _argv ){ + _climenu_option * self = (_climenu_option*) _self; + + if( _argc <= 0 || _argv[0] == NULL ){ + printf("%s is %f\n", self->name, *(float*)(self->var)); + } else { + sscanf(_argv[0], "%f", (float *)self->var); + printf("%s is %f\n", self->name, *(float*)(self->var)); + } + return SH_CONTINUE; // continue + +} +int toggleVariable( + __attribute__((unused)) void * _self, + __attribute__((unused)) int _argc, + __attribute__((unused)) char ** _argv ){ + _climenu_option * self = (_climenu_option*) _self; + *(int*)self->var = ! *(int*)self->var; //yeah, maybe this got a little out of hand + return SH_CONTINUE; // continue + +} +int shell_sleep( __attribute__((unused)) void * _self, int _argc, char ** _argv ){ + if( ! _argc || _argv[0] == NULL ){ + printf("Provide a number in milliseconds to sleep as an argument\n"); + return SH_ERR; + } + useconds_t sleepus = atoi(_argv[0]) * 1000; + usleep(sleepus); + return SH_CONTINUE; +} +int shell_quit( + __attribute__((unused)) void * _self, + __attribute__((unused)) int _argc, + __attribute__((unused)) char ** _argv ){ + printf("QUIT: 73!\n"); + //could remove history entries here, if we wanted + return SH_EXIT_OK; //normal quit +} +int printState( + __attribute__((unused)) void * _self, + __attribute__((unused)) int _argc, + __attribute__((unused)) char **_argv) { printf("\nCurrent state\n"); printf("RSSI : %f\n", Radio_State.RSSI); @@ -64,43 +283,163 @@ void printState() printf("Volume : %f\n", Radio_State.volumeLevel); printf("Channel: %f\n", Radio_State.chSelector); printf("PTT : %s\n\n", Radio_State.PttStatus ? "true" : "false"); - + return SH_CONTINUE; +} +int shell_nop( + __attribute__((unused)) void * _self, + __attribute__((unused)) int _argc, + __attribute__((unused)) char ** _argv ){ + //do nothing! what it says on the tin + return SH_CONTINUE; } +int shell_help(void * _self, int _argc, char ** _argv ); + +_climenu_option _options[] = { +/* name/shortcut description var reference, if available method to call */ + {"rssi", "Set rssi", (void*)&Radio_State.RSSI, setFloat }, + {"vbat", "Set vbat", (void*)&Radio_State.Vbat, setFloat }, + {"mic", "Set miclevel", (void*)&Radio_State.micLevel, setFloat }, + {"volume", "Set volume", (void*)&Radio_State.volumeLevel,setFloat }, + {"channel", "Set channel", (void*)&Radio_State.chSelector, setFloat }, + {"ptt", "Toggle PTT", (void*)&Radio_State.PttStatus, toggleVariable }, + {"key", "Press keys in sequence (e.g. 'key ENTER DOWN ENTER' will descend through two menus)", + NULL, pressKey }, + {"keycombo", "Press a bunch of keys simultaneously ", + NULL, pressMultiKeys }, + {"show", "Show current radio state (ptt, rssi, etc)", + NULL, printState }, + + {"screenshot","[screenshot.bmp] Save screenshot to first arg or screenshot.bmp if none given", + NULL, screenshot }, + /*{"record_start", "[screen.mkv] Automatically save a video of the remaining session (or until record_stop is called)",*/ + /*NULL, record_start },*/ + /*{"record_stop", "Stop the running recording, or no-op if none started",*/ + /*NULL, record_stop },*/ + {"sleep", "Wait some number of ms", NULL, shell_sleep }, + {"help", "Print this help", NULL, shell_help }, + {"nop", "Do nothing (useful for comments)", + NULL, shell_nop }, + /*{"ready", */ + /*"Wait until ready. Currently supports keyboard, so will wait until all keyboard events are processed,"*/ + /*"but is already implied by key and keycombo so there's not much direct use for it right now",*/ + /*NULL, shell_ready },*/ + {"quit", "Quit, close the emulator", NULL, shell_quit }, +}; +int num_options = (sizeof( _options )/ sizeof(_climenu_option)); + + +int shell_help( + __attribute__((unused)) void * _self, + __attribute__((unused)) int _argc, + __attribute__((unused)) char ** _argv ){ + printf("OpenRTX emulator shell\n\n"); + for( int i = 0; i < num_options; i++ ){ + _climenu_option * o = &_options[i]; + printf("%10s -> %s\n", o->name, o->description); + } + return SH_CONTINUE; +} + + +_climenu_option * findMenuOption(char * tok){ + for( int i = 0; i < num_options; i++ ){ + _climenu_option * o = &_options[i]; + if( strncmp(tok, o->name, strlen(tok)) == 0 ){ + //strncmp like this allows for typing shortcuts like just "r" instead of the full "rssi" + //priority for conflicts (like if there's "s" which could mean + // either "show" or "screenshot" ) + //is set by ordering in the _options array + return o; + } + } + return NULL; +} + +void striptoken(char * token){ + for( size_t i = 0; i < strlen(token); i++ ){ + if( token[i] == '\n' ){ + token[i] = 0; + } + } +} +int process_line(char * line){ + char * token = strtok( line, " "); + if( token == NULL ){ + return SH_ERR; + } + striptoken(token); + _climenu_option * o = findMenuOption(token); + char * args[12] = {NULL}; + int i = 0; + for( i = 0; i < 12; i++ ){ + //immediately strtok again since first is a command rest are args + token = strtok(NULL, " "); + if( token == NULL ){ + break; + } + striptoken(token); + args[i] = token; + } + if( token != NULL ){ + printf("\nGot too many arguments, args truncated \n"); + } + if( o != NULL ){ + if( o->fn != NULL ){ + return o->fn(o, i, args); + } else { + printf("Bad fn for o, check option array for bad data\n"); + return SH_ERR; + } + } else { + return SH_WHAT; //not understood + } +} void *startCLIMenu() { - int choice; - do - { - choice = CLIMenu(); - switch (choice) - { - case VAL_RSSI: - updateValue(&Radio_State.RSSI); - break; - case VAL_BAT: - updateValue(&Radio_State.Vbat); - break; - case VAL_MIC: - updateValue(&Radio_State.micLevel); - break; - case VAL_VOL: - updateValue(&Radio_State.volumeLevel); - break; - case VAL_CH: - updateValue(&Radio_State.chSelector); - break; - case VAL_PTT: - Radio_State.PttStatus = Radio_State.PttStatus ? false : true; - break; - case PRINT_STATE: - printState(); - break; - default: - continue; + printf("\n\n"); + char * histfile = ".emulatorsh_history"; + shell_help(NULL,0,NULL); + /*printf("\n> ");*/ + int ret = SH_CONTINUE; + using_history(); + read_history(histfile); + do { + /*char * r = fgets(shellbuf, 255, stdin);*/ + char * r = readline(">"); + if( r == NULL ){ + ret = SH_EXIT_OK; + } else if( strlen(r) > 0 ){ + add_history(r); + ret = process_line(r); + } else { + ret = SH_CONTINUE; } - } while (choice != EXIT); - printf("73\n"); + switch(ret){ + default: + fflush(stdout); + break; + case SH_WHAT: + printf("?\n(type h or help for help)\n"); + ret = SH_CONTINUE; //i'd rather just fall through, but the compiler warns. blech. + /*printf("\n>");*/ + break; + case SH_CONTINUE: + /*printf("\n>");*/ + break; + case SH_EXIT_OK: + //normal quit + break; + case SH_ERR: + //error + printf("Error running that command\n"); + ret = SH_CONTINUE; + break; + } + free(r); //free the string allocated by readline + } while ( ret == SH_CONTINUE ); + fflush(stdout); + write_history(histfile); exit(0); }