|
|
… |
44 unchanged lines |
| 45 |
45 |
|
static long g_frames_to_run = -1; |
| 46 |
46 |
|
static int g_step_waiting = 0; |
| 47 |
47 |
|
|
| 48 |
|
- |
/* single-slot marshalled request (mem / regs / screenshot) */ |
| 49 |
|
- |
typedef enum { REQ_NONE = 0, REQ_MEM, REQ_REGS, REQ_SHOT } req_t; |
|
48 |
+ |
/* single-slot marshalled request (mem / regs / screenshot / key / reset) */ |
|
49 |
+ |
typedef enum { REQ_NONE = 0, REQ_MEM, REQ_REGS, REQ_SHOT, REQ_KEY, REQ_RESET } req_t; |
| 50 |
50 |
|
static volatile req_t g_req = REQ_NONE; |
| 51 |
51 |
|
static uint32_t g_req_addr, g_req_len; |
| 52 |
52 |
|
static int32_t g_req_bank; |
|
53 |
+ |
static int g_key_is_text; /* 1 => g_key_value is a char code point */ |
|
54 |
+ |
static uint32_t g_key_value; |
|
55 |
+ |
static int g_key_action; /* retro_key_action_t */ |
| 53 |
56 |
|
|
| 54 |
57 |
|
static uint8_t *g_resp_body = NULL; |
| 55 |
58 |
|
static size_t g_resp_len = 0; |
|
|
… |
64 unchanged lines |
| 120 |
123 |
|
if (g_be->get_framebuffer) build_ppm(); |
| 121 |
124 |
|
else { g_resp_body = NULL; g_resp_len = 0; g_resp_status = 501; } |
| 122 |
125 |
|
break; |
|
126 |
+ |
case REQ_KEY: { |
|
127 |
+ |
const char *json; |
|
128 |
+ |
if (!g_be->inject_key) { |
|
129 |
+ |
json = "{\"error\":\"not implemented\"}"; g_resp_status = 501; |
|
130 |
+ |
} else if (g_be->inject_key(g_key_is_text, g_key_value, g_key_action)) { |
|
131 |
+ |
json = "{\"injected\":true}"; g_resp_status = 200; |
|
132 |
+ |
} else { |
|
133 |
+ |
json = "{\"error\":\"unmapped key\"}"; g_resp_status = 400; |
|
134 |
+ |
} |
|
135 |
+ |
size_t l = strlen(json); |
|
136 |
+ |
uint8_t *b = (uint8_t *)malloc(l + 1); |
|
137 |
+ |
if (b) memcpy(b, json, l + 1); |
|
138 |
+ |
g_resp_body = b; g_resp_len = b ? l : 0; |
|
139 |
+ |
g_resp_ctype = "application/json"; |
|
140 |
+ |
break; |
|
141 |
+ |
} |
|
142 |
+ |
case REQ_RESET: { |
|
143 |
+ |
const char *json; |
|
144 |
+ |
if (g_be->reset) { g_be->reset(); json = "{\"reset\":true}"; g_resp_status = 200; } |
|
145 |
+ |
else { json = "{\"error\":\"not implemented\"}"; g_resp_status = 501; } |
|
146 |
+ |
size_t l = strlen(json); |
|
147 |
+ |
uint8_t *b = (uint8_t *)malloc(l + 1); |
|
148 |
+ |
if (b) memcpy(b, json, l + 1); |
|
149 |
+ |
g_resp_body = b; g_resp_len = b ? l : 0; |
|
150 |
+ |
g_resp_ctype = "application/json"; |
|
151 |
+ |
break; |
|
152 |
+ |
} |
| 123 |
153 |
|
default: break; |
| 124 |
154 |
|
} |
| 125 |
155 |
|
g_req = REQ_NONE; |
|
|
… |
75 unchanged lines |
| 201 |
231 |
|
return def; |
| 202 |
232 |
|
} |
| 203 |
233 |
|
|
|
234 |
+ |
/* copy the value of query key into out (NUL-terminated, truncated to cap). |
|
235 |
+ |
* Returns 1 if the key was present, 0 otherwise. */ |
|
236 |
+ |
static int query_str(const char *q, const char *key, char *out, size_t cap) |
|
237 |
+ |
{ |
|
238 |
+ |
size_t kl = strlen(key); |
|
239 |
+ |
const char *p = q; |
|
240 |
+ |
while (p && *p) { |
|
241 |
+ |
if (!strncmp(p, key, kl) && p[kl] == '=') { |
|
242 |
+ |
const char *v = p + kl + 1; |
|
243 |
+ |
const char *e = strchr(v, '&'); |
|
244 |
+ |
size_t n = e ? (size_t)(e - v) : strlen(v); |
|
245 |
+ |
if (n >= cap) n = cap - 1; |
|
246 |
+ |
memcpy(out, v, n); |
|
247 |
+ |
out[n] = 0; |
|
248 |
+ |
return 1; |
|
249 |
+ |
} |
|
250 |
+ |
p = strchr(p, '&'); |
|
251 |
+ |
if (p) p++; |
|
252 |
+ |
} |
|
253 |
+ |
if (cap) out[0] = 0; |
|
254 |
+ |
return 0; |
|
255 |
+ |
} |
|
256 |
+ |
|
| 204 |
257 |
|
/* marshal a read to the emulator thread, wait, send the response. */ |
| 205 |
258 |
|
static void do_marshalled(int fd, req_t r) |
| 206 |
259 |
|
{ |
|
|
… |
16 unchanged lines |
| 223 |
276 |
|
long f = g_frames_to_run; |
| 224 |
277 |
|
pthread_mutex_unlock(&g_lock); |
| 225 |
278 |
|
unsigned long long fc = g_be->get_frame_count ? g_be->get_frame_count() : 0; |
|
279 |
+ |
/* Advertise 0.2 only when the backend actually offers input injection. */ |
|
280 |
+ |
const char *contract = g_be->inject_key ? "0.2.0" : "0.1.0"; |
| 226 |
281 |
|
char buf[320]; |
| 227 |
282 |
|
snprintf(buf, sizeof buf, |
| 228 |
283 |
|
"{\"contract\":\"%s\",\"emulator\":\"%s\",\"platform\":\"%s\"," |
| 229 |
284 |
|
"\"frame\":%llu,\"paused\":%s,\"running\":true}", |
| 230 |
|
- |
RETRO_CONTROL_CONTRACT, |
|
285 |
+ |
contract, |
| 231 |
286 |
|
g_be->emulator ? g_be->emulator : "", |
| 232 |
287 |
|
g_be->platform ? g_be->platform : "", |
| 233 |
288 |
|
fc, (f == 0) ? "true" : "false"); |
|
|
… |
68 unchanged lines |
| 302 |
357 |
|
if (!strcmp(target, "/resume")) { |
| 303 |
358 |
|
if (!is_post) { send_json(fd, 405, "{\"error\":\"POST only\"}"); return; } |
| 304 |
359 |
|
do_setrun(fd, -1, 0); return; |
|
360 |
+ |
} |
|
361 |
+ |
if (!strcmp(target, "/key")) { |
|
362 |
+ |
if (!is_post) { send_json(fd, 405, "{\"error\":\"POST only\"}"); return; } |
|
363 |
+ |
char text[8]; |
|
364 |
+ |
long code = query_long(query, "code", -1); |
|
365 |
+ |
long down = query_long(query, "down", -1); |
|
366 |
+ |
if (query_str(query, "text", text, sizeof text) && text[0]) { |
|
367 |
+ |
g_key_is_text = 1; g_key_value = (uint32_t)(unsigned char)text[0]; |
|
368 |
+ |
} else if (code >= 0) { |
|
369 |
+ |
g_key_is_text = 0; g_key_value = (uint32_t)code; |
|
370 |
+ |
} else { |
|
371 |
+ |
send_json(fd, 400, "{\"error\":\"key needs text= or code=\"}"); return; |
|
372 |
+ |
} |
|
373 |
+ |
g_key_action = (down < 0) ? RETRO_KEY_TAP |
|
374 |
+ |
: (down != 0) ? RETRO_KEY_DOWN : RETRO_KEY_UP; |
|
375 |
+ |
do_marshalled(fd, REQ_KEY); |
|
376 |
+ |
return; |
|
377 |
+ |
} |
|
378 |
+ |
if (!strcmp(target, "/reset")) { |
|
379 |
+ |
if (!is_post) { send_json(fd, 405, "{\"error\":\"POST only\"}"); return; } |
|
380 |
+ |
do_marshalled(fd, REQ_RESET); |
|
381 |
+ |
return; |
| 305 |
382 |
|
} |
| 306 |
383 |
|
send_json(fd, 404, "{\"error\":\"not found\"}"); |
| 307 |
384 |
|
} |
|
|
… |
55 unchanged lines |