diff --git a/radio/src/targets/simu/CMakeLists.txt b/radio/src/targets/simu/CMakeLists.txt index b6c391d42..84bf014b4 100644 --- a/radio/src/targets/simu/CMakeLists.txt +++ b/radio/src/targets/simu/CMakeLists.txt @@ -172,6 +172,7 @@ if(TARGET SDL2:SDL2) display.cpp widgets.cpp knobs.cpp + arg_parser.cpp sdl_simu.cpp ) diff --git a/radio/src/targets/simu/arg_parser.cpp b/radio/src/targets/simu/arg_parser.cpp new file mode 100644 index 000000000..747a18afd --- /dev/null +++ b/radio/src/targets/simu/arg_parser.cpp @@ -0,0 +1,117 @@ +#include "arg_parser.h" + +#include +#include +#include +#include + +ArgumentParser::ArgumentParser(const std::string &prog_name) + : program_name(prog_name) {} + +bool ArgumentParser::parse(int argc, char *argv[]) { + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + + if (arg == "--width") { + if (!parseIntOption(argc, argv, i, width, "width")) + return false; + } else if (arg == "--height") { + if (!parseIntOption(argc, argv, i, height, "height")) + return false; + } else if (arg == "--storage") { + if (!getNextArg(argc, argv, i, storage_path, "storage")) + return false; + } else if (arg == "--settings") { + if (!getNextArg(argc, argv, i, settings_path, "settings")) + return false; + } else if (arg == "-h" || arg == "--help") { + help_requested = true; + return true; + } else { + printf("Unknown option: %s\n", arg.c_str()); + printUsage(); + return false; + } + } + return true; +} + +void ArgumentParser::printUsage() const { + printf("usage: %s [--width width] [--height height] [--storage path] " + "[--settings path] [-h | --help]\n", + program_name.c_str()); +} + +void ArgumentParser::printHelp() const { + printUsage(); + printf("\nOptions:\n"); + printf(" --width width Set the width (integer)\n"); + printf(" --height height Set the height (integer)\n"); + printf(" --storage path Set the storage path\n"); + printf(" --settings path Set the settings path\n"); + printf(" -h, --help Show this help message\n"); +} + +bool ArgumentParser::isHelpRequested() const { return help_requested; } + +int ArgumentParser::getWidth() const { return width; } + +int ArgumentParser::getHeight() const { return height; } + +const std::string &ArgumentParser::getStoragePath() const { + return storage_path; +} + +const std::string &ArgumentParser::getSettingsPath() const { + return settings_path; +} + +bool ArgumentParser::hasWidth() const { return width != -1; } + +bool ArgumentParser::hasHeight() const { return height != -1; } + +bool ArgumentParser::hasStoragePath() const { return !storage_path.empty(); } + +bool ArgumentParser::hasSettingsPath() const { return !settings_path.empty(); } + +bool ArgumentParser::getNextArg(int argc, char *argv[], int &i, + std::string &value, + const std::string &option_name) { + if (i + 1 >= argc) { + printf("Option --%s requires an argument\n", option_name.c_str()); + printUsage(); + return false; + } + value = argv[++i]; + return true; +} + +bool ArgumentParser::parseIntOption(int argc, char *argv[], int &i, int &value, + const std::string &option_name) { + if (i + 1 >= argc) { + printf("Option --%s requires an argument\n", option_name.c_str()); + printUsage(); + return false; + } + + char *endptr; + const char *str = argv[++i]; + + // Check if string is empty or starts with non-digit (except for optional '+') + if (!str || *str == '\0' || (!isdigit(*str) && *str != '+')) { + printf("Option --%s requires a valid integer\n", option_name.c_str()); + return false; + } + + long result = strtol(str, &endptr, 10); + + // Check for conversion errors + if (*endptr != '\0' || result <= 0 || result > INT_MAX) { + printf("Option --%s requires a valid positive integer\n", + option_name.c_str()); + return false; + } + + value = static_cast(result); + return true; +} diff --git a/radio/src/targets/simu/arg_parser.h b/radio/src/targets/simu/arg_parser.h new file mode 100644 index 000000000..359cc6625 --- /dev/null +++ b/radio/src/targets/simu/arg_parser.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +class ArgumentParser { +private: + int width = -1; + int height = -1; + std::string storage_path; + std::string settings_path; + bool help_requested = false; + std::string program_name; + +public: + ArgumentParser(const std::string &prog_name); + + bool parse(int argc, char *argv[]); + void printUsage() const; + void printHelp() const; + + // Getters + bool isHelpRequested() const; + int getWidth() const; + int getHeight() const; + const std::string &getStoragePath() const; + const std::string &getSettingsPath() const; + + // Check if option was provided + bool hasWidth() const; + bool hasHeight() const; + bool hasStoragePath() const; + bool hasSettingsPath() const; + +private: + bool getNextArg(int argc, char *argv[], int &i, std::string &value, + const std::string &option_name); + bool parseIntOption(int argc, char *argv[], int &i, int &value, + const std::string &option_name); +}; diff --git a/radio/src/targets/simu/bt_driver.cpp b/radio/src/targets/simu/bt_driver.cpp index 032fae5bc..25c66a7e6 100644 --- a/radio/src/targets/simu/bt_driver.cpp +++ b/radio/src/targets/simu/bt_driver.cpp @@ -25,6 +25,6 @@ void bluetoothInit(unsigned int, bool) {} void bluetoothWrite(const void*, uint32_t) {} int bluetoothRead(uint8_t*) { return 0; } void bluetoothDisable() {} -bool bluetoothIsWriting() { return false; } +uint8_t bluetoothIsWriting() { return false; } volatile uint8_t btChipPresent; diff --git a/radio/src/targets/simu/sdl_simu.cpp b/radio/src/targets/simu/sdl_simu.cpp index 960b68ede..d8e6b8a7e 100644 --- a/radio/src/targets/simu/sdl_simu.cpp +++ b/radio/src/targets/simu/sdl_simu.cpp @@ -19,6 +19,7 @@ * GNU General Public License for more details. */ +#define SDL_MAIN_HANDLED #include #include @@ -26,12 +27,10 @@ #include #include -#include -#include - #include #include #include +#include #include #include @@ -72,14 +71,10 @@ #include "debug.h" #include "edgetx.h" +#include "arg_parser.h" + #define TIMER_INTERVAL 10 // 10ms -int window_width = 800; -int window_height = 600; - -std::string storage_path; -std::string settings_path; - static SDL_Window* window; static SDL_Renderer* renderer; static SDL_Texture* screen_frame_buffer; @@ -677,81 +672,30 @@ int find_input_mode() return default_input_mode(); } -static void print_usage(const char* path) -{ - const char usage[] = - "usage: %s [--width width] [--height height] [--storage path] " - "[--settings path] [-h | --help]\n"; - - printf(usage, basename((char*)path)); -} - -static int parse_args(int argc, char* argv[]) -{ - static struct option long_options[] = { - {"help", no_argument, 0, 'h'}, - {"width", required_argument, 0, 'w'}, - {"height", required_argument, 0, 'e'}, - {"storage", required_argument, 0, 's'}, - {"settings", required_argument, 0, 'c'}, - {0, 0, 0, 0}}; - - int option_index = 0; - int c; - - while ((c = getopt_long(argc, argv, "h", long_options, &option_index)) != - -1) { - switch (c) { - case 'h': - print_usage(argv[0]); - return 1; - - case 'w': - window_width = std::atoi(optarg); - break; - - case 'e': - window_height = std::atoi(optarg); - break; - - case 's': - storage_path = std::string(optarg); - break; - - case 'c': // 'c' for config/settings - settings_path = std::string(optarg); - break; - - case '?': - // getopt_long already printed an error message - print_usage(argv[0]); - return 1; - - default: - abort(); - } - } - - // Check for non-option arguments - if (optind < argc) { - printf("Error: Unexpected arguments: "); - while (optind < argc) { - printf("%s ", argv[optind++]); - } - printf("\n"); - print_usage(argv[0]); - return 1; - } - - return 0; -} - int main(int argc, char* argv[]) { - if (parse_args(argc, argv) != 0) { + auto progname = std::filesystem::path(argv[0]).filename(); + ArgumentParser args(progname.string()); + + if (!args.parse(argc, argv)) { return 1; } + if (args.isHelpRequested()) { + args.printHelp(); + return 0; + } + + int window_height = 600; + if (args.hasHeight()) { + window_height = args.getHeight(); + } + + int window_width = 800; + if (args.hasWidth()) { + window_width = args.getWidth(); + } + // Setup SDL if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER) != 0) { printf("Error: %s\n", SDL_GetError()); @@ -829,8 +773,9 @@ int main(int argc, char* argv[]) // Init simulation simuInit(); - simuStart(true, storage_path.c_str(), settings_path.c_str()); - + simuStart(true, args.getStoragePath().c_str(), + args.getSettingsPath().c_str()); + // Main loop SDL_SetEventFilter([](void*, SDL_Event* event){ if (event->type == SDL_WINDOWEVENT && diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index 3f755c927..22b49c7a7 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -69,35 +69,8 @@ void lcdCopy(void * dest, void * src); uint64_t simuTimerMicros(void) { -#if SIMPGMSPC_USE_QT - static QElapsedTimer ticker; - if (!ticker.isValid()) - ticker.start(); - return ticker.nsecsElapsed() / 1000; - -#elif defined(_MSC_VER) - static double freqScale = 0.0; - static LARGE_INTEGER firstTick; - LARGE_INTEGER newTick; - - if (!freqScale) { - LARGE_INTEGER frequency; - // get ticks per second - QueryPerformanceFrequency(&frequency); - // 1us resolution - freqScale = 1e6 / frequency.QuadPart; - // init timer - QueryPerformanceCounter(&firstTick); - TRACE_SIMPGMSPACE("microsTimer() init: first tick = %llu @ %llu Hz", firstTick.QuadPart, frequency.QuadPart); - } - // read the timer - QueryPerformanceCounter(&newTick); - // compute the elapsed time - return (newTick.QuadPart - firstTick.QuadPart) * freqScale; -#else // GNUC auto now = std::chrono::steady_clock::now(); return (uint64_t) std::chrono::duration_cast(now.time_since_epoch()).count(); -#endif } uint16_t getTmr16KHz() diff --git a/radio/src/targets/simu/simpgmspace.h b/radio/src/targets/simu/simpgmspace.h index cd3c9bf3d..ac17f9caa 100644 --- a/radio/src/targets/simu/simpgmspace.h +++ b/radio/src/targets/simu/simpgmspace.h @@ -21,9 +21,6 @@ #pragma once -#include -#define sleep(x) usleep(1000*x) - #include #include #include