|
10 | 10 | #define BUGS_ADDRESS "https://github.com/X65/emu/issues" |
11 | 11 | const char* app_bug_address = BUGS_ADDRESS; |
12 | 12 | const char* app_releases_address = "https://github.com/X65/emu/releases"; |
13 | | -#define FULL_NAME "X65 microcomputer emulator" |
| 13 | +#define SHORT_NAME "emu" |
| 14 | +#define FULL_NAME "X65 microcomputer emulator" |
14 | 15 | const char full_name[] = FULL_NAME; |
15 | 16 |
|
16 | 17 | struct arguments arguments = { |
@@ -62,7 +63,7 @@ static const option_t options[] = { |
62 | 63 | #define HELP_LINE_WIDTH 79 // total line width used for word-wrapping |
63 | 64 |
|
64 | 65 | static void print_usage(FILE* f) { |
65 | | - fprintf(f, "Usage: emu [OPTION...] %s\n", args_doc); |
| 66 | + fprintf(f, "Usage: " SHORT_NAME " [OPTION...] %s\n", args_doc); |
66 | 67 | } |
67 | 68 |
|
68 | 69 | static bool key_is_short(int key) { |
@@ -168,9 +169,117 @@ static void print_help(void) { |
168 | 169 | printf("\nReport bugs to: %s\n", app_bug_address); |
169 | 170 | } |
170 | 171 |
|
| 172 | +#ifdef __EMSCRIPTEN__ |
| 173 | + #include <ctype.h> |
| 174 | + #include <emscripten/emscripten.h> |
| 175 | + |
| 176 | + // keep the JS runtime helpers used by the EM_JS below alive through closure |
| 177 | + #if defined(EM_JS_DEPS) |
| 178 | +EM_JS_DEPS(x65args, "$withStackSave,$stringToUTF8OnStack"); |
| 179 | + #endif |
| 180 | + |
| 181 | +// The page URL query string, captured from JS into a C string. |
| 182 | +static char* web_query = NULL; |
| 183 | + |
| 184 | +EMSCRIPTEN_KEEPALIVE void args_store_query(const char* q) { |
| 185 | + web_query = strdup(q ? q : ""); |
| 186 | +} |
| 187 | + |
| 188 | +// Read window.location.search and hand it to args_store_query() as a C string. |
| 189 | +// clang-format off |
| 190 | +EM_JS(void, args_js_read_query, (void), { |
| 191 | + var search = window.location.search || ""; |
| 192 | + withStackSave(function() { _args_store_query(stringToUTF8OnStack(search)); }); |
| 193 | +}) |
| 194 | +// clang-format on |
| 195 | + |
| 196 | +// Decode %XX escapes and '+' in place (application/x-www-form-urlencoded style). |
| 197 | +static void url_decode(char* s) { |
| 198 | + char* o = s; |
| 199 | + for (char* p = s; *p;) { |
| 200 | + if (*p == '%' && isxdigit((unsigned char)p[1]) && isxdigit((unsigned char)p[2])) { |
| 201 | + char hex[3] = { p[1], p[2], 0 }; |
| 202 | + *o++ = (char)strtol(hex, NULL, 16); |
| 203 | + p += 3; |
| 204 | + } |
| 205 | + else if (*p == '+') { |
| 206 | + *o++ = ' '; |
| 207 | + p++; |
| 208 | + } |
| 209 | + else { |
| 210 | + *o++ = *p++; |
| 211 | + } |
| 212 | + } |
| 213 | + *o = '\0'; |
| 214 | +} |
| 215 | + |
| 216 | +// Convert the page URL query string into an argv array parseable by optparse: |
| 217 | +// ?fullscreen&crt=1,2,3&file=rom.xex -> { prog, "--fullscreen", "--crt=1,2,3", "rom.xex", NULL } |
| 218 | +// Rules: split on '&'; "file=VALUE" becomes a bare positional (may repeat, for |
| 219 | +// many-files support); every other token that isn't already "--..." is given a |
| 220 | +// "--" prefix (only the long "--option[=value]" form is supported). |
| 221 | +static char** web_build_argv(char* prog) { |
| 222 | + // window.location.search, e.g. "?file=rom.xex&crt=1,2,3&fullscreen" |
| 223 | + args_js_read_query(); |
| 224 | + const char* search = web_query ? web_query : ""; |
| 225 | + if (search[0] == '?') search++; |
| 226 | + char* buf = strdup(search); |
| 227 | + |
| 228 | + int cap = 8, argc = 0; |
| 229 | + char** argv = malloc((size_t)cap * sizeof(char*)); |
| 230 | + #define PUSH(S) \ |
| 231 | + do { \ |
| 232 | + if (argc + 1 >= cap) { \ |
| 233 | + cap *= 2; \ |
| 234 | + argv = realloc(argv, (size_t)cap * sizeof(char*)); \ |
| 235 | + } \ |
| 236 | + argv[argc++] = (S); \ |
| 237 | + } while (0) |
| 238 | + |
| 239 | + PUSH(prog ? prog : (char*)SHORT_NAME); // argv[0] is the program name |
| 240 | + |
| 241 | + char* p = buf; |
| 242 | + while (*p) { |
| 243 | + char* tok = p; |
| 244 | + char* amp = strchr(p, '&'); |
| 245 | + if (amp) { |
| 246 | + *amp = '\0'; |
| 247 | + p = amp + 1; |
| 248 | + } |
| 249 | + else { |
| 250 | + p += strlen(p); |
| 251 | + } |
| 252 | + if (!tok[0]) continue; |
| 253 | + url_decode(tok); |
| 254 | + |
| 255 | + if (strncmp(tok, "file=", 5) == 0) { |
| 256 | + if (tok[5]) PUSH(tok + 5); // positional file argument |
| 257 | + } |
| 258 | + else if (strncmp(tok, "--", 2) == 0) { |
| 259 | + PUSH(tok); // already in long form |
| 260 | + } |
| 261 | + else { |
| 262 | + size_t n = strlen(tok) + 3; |
| 263 | + char* opt = malloc(n); |
| 264 | + snprintf(opt, n, "--%s", tok); |
| 265 | + PUSH(opt); |
| 266 | + } |
| 267 | + } |
| 268 | + PUSH(NULL); // optparse expects a NULL-terminated argv |
| 269 | + #undef PUSH |
| 270 | + |
| 271 | + return argv; |
| 272 | +} |
| 273 | +#endif // __EMSCRIPTEN__ |
| 274 | + |
171 | 275 | void args_parse(int argc, char* argv[]) { |
172 | 276 | (void)argc; |
173 | 277 |
|
| 278 | +#ifdef __EMSCRIPTEN__ |
| 279 | + // On the web there is no command line; arguments come from the page URL. |
| 280 | + argv = web_build_argv(argc > 0 ? argv[0] : NULL); |
| 281 | +#endif |
| 282 | + |
174 | 283 | // Build the optparse long-option table from our single source of truth, |
175 | 284 | // plus the synthetic --help/--version entries argp-like. |
176 | 285 | struct optparse_long longopts[sizeof(options) / sizeof(options[0]) + 2]; |
@@ -219,20 +328,13 @@ void args_parse(int argc, char* argv[]) { |
219 | 328 |
|
220 | 329 | case '?': |
221 | 330 | fprintf(stderr, "%s: %s\n", app_name, opt.errmsg); |
222 | | - fprintf(stderr, "Try 'emu --help' for more information.\n"); |
| 331 | + fprintf(stderr, "Try '" SHORT_NAME " --help' for more information.\n"); |
223 | 332 | exit(EXIT_FAILURE); |
224 | 333 | } |
225 | 334 | } |
226 | 335 |
|
227 | 336 | const char* pos; |
228 | | - bool rom_set = false; |
229 | 337 | while ((pos = optparse_arg(&opt))) { |
230 | | - if (rom_set) { |
231 | | - fprintf(stderr, "%s: too many arguments\n", app_name); |
232 | | - print_usage(stderr); |
233 | | - exit(EXIT_FAILURE); |
234 | | - } |
235 | | - arguments.rom = pos; |
236 | | - rom_set = true; |
| 338 | + if (!arguments.rom) arguments.rom = pos; // first positional file |
237 | 339 | } |
238 | 340 | } |
0 commit comments