|
|
1.1 root 1: /*
2: Hatari - control.c
3:
4: This file is distributed under the GNU Public License, version 2 or at
5: your option any later version. Read the file gpl.txt for details.
6:
7: This code processes commands from the Hatari control socket
8: */
9: const char Control_fileid[] = "Hatari control.c : " __DATE__ " " __TIME__;
10:
11: #include "config.h"
12:
13: #if HAVE_UNIX_DOMAIN_SOCKETS
14: # include <sys/socket.h>
15: # include <sys/un.h>
16: #endif
17:
18: #include <sys/types.h>
19: #include <sys/time.h>
20: #include <unistd.h>
21: #include <ctype.h>
22:
23: #include "main.h"
24: #include "change.h"
25: #include "configuration.h"
26: #include "control.h"
27: #include "debugui.h"
28: #include "file.h"
29: #include "log.h"
30: #include "shortcut.h"
31: #include "str.h"
32:
33: typedef enum {
34: DO_DISABLE,
35: DO_ENABLE,
36: DO_TOGGLE
37: } action_t;
38:
39: /* Whether to send embedded window info */
40: static bool bSendEmbedInfo;
41: /* Pausing triggered remotely (battery save pause) */
42: static bool bRemotePaused;
43:
44:
45: /*-----------------------------------------------------------------------*/
46: /**
47: * Parse key command and synthetize key press/release
48: * corresponding to given keycode or character.
49: * Return false if parsing failed, true otherwise
50: *
51: * This can be used by external Hatari UI(s) for
52: * string macros, or on devices which lack keyboard
53: */
54: static bool Control_InsertKey(const char *event)
55: {
56: const char *key = NULL;
57: bool press;
58:
59: if (strncmp(event, "keypress ", 9) == 0) {
60: key = &event[9];
61: press = true;
62: } else if (strncmp(event, "keyrelease ", 11) == 0) {
63: key = &event[11];
64: press = false;
65: }
66: if (!(key && key[0])) {
67: fprintf(stderr, "ERROR: event '%s' contains no key press/release\n", event);
68: return false;
69: }
70: if (key[1]) {
71: char *endptr;
72: /* multiple characters, assume it's a keycode */
73: int keycode = strtol(key, &endptr, 0);
74: /* not a valid number or keycode is out of range? */
75: if (*endptr || keycode < 0 || keycode > 255) {
76: fprintf(stderr, "ERROR: '%s' is not valid key code, got %d\n",
77: key, keycode);
78: return false;
79: }
80: // IKBD_PressSTKey(keycode, press);
81: } else {
82: // Keymap_SimulateCharacter(key[0], press);
83: }
84: fprintf(stderr, "Simulated %s key %s\n",
85: key, (press?"press":"release"));
86: return true;
87: }
88:
89: /*-----------------------------------------------------------------------*/
90: /**
91: * Parse event name and synthetize corresponding event to emulation
92: * Return false if name parsing failed, true otherwise
93: *
94: * This can be used by external Hatari UI(s) on devices which input
95: * methods differ from normal keyboard and mouse, such as high DPI
96: * touchscreen (no right/middle button, inaccurate clicks)
97: */
98: static bool Control_InsertEvent(const char *event)
99: {
100: if (strcmp(event, "doubleclick") == 0) {
101: // Keyboard.LButtonDblClk = 1;
102: return true;
103: }
104: if (strcmp(event, "rightpress") == 0) {
105: // Keyboard.bRButtonDown |= BUTTON_MOUSE;
106: return true;
107: }
108: if (strcmp(event, "rightrelease") == 0) {
109: // Keyboard.bRButtonDown &= ~BUTTON_MOUSE;
110: return true;
111: }
112: if (Control_InsertKey(event)) {
113: return true;
114: }
115: fprintf(stderr, "ERROR: unrecognized event: '%s'\n", event);
116: fprintf(stderr, "Supported events are:\n");
117: fprintf(stderr, "- doubleclick\n");
118: fprintf(stderr, "- rightpress\n");
119: fprintf(stderr, "- rightrelease\n");
120: fprintf(stderr, "- keypress <character>\n");
121: fprintf(stderr, "- keyrelease <character>\n");
122: fprintf(stderr, "<character> can be either a single ASCII char or keycode.\n");
123: return false;
124: }
125:
126: /*-----------------------------------------------------------------------*/
127: /**
128: * Parse device name and enable/disable/toggle & init/uninit it according
129: * to action. Return false if name parsing failed, true otherwise
130: */
131: static bool Control_DeviceAction(const char *name, action_t action)
132: {
133: /* Note: e.g. RTC would require restarting emulation
134: * and HD-boot setting emulation reboot. Devices
135: * listed here work just with init/uninit.
136: */
137: struct {
138: const char *name;
139: bool *pvalue;
140: void(*init)(void);
141: void(*uninit)(void);
142: } item[] = {
143: { NULL, NULL, NULL, NULL }
144: };
145: int i;
146: bool value;
147: for (i = 0; item[i].name; i++)
148: {
149: if (strcmp(name, item[i].name) == 0)
150: {
151: switch (action) {
152: case DO_TOGGLE:
153: value = !*(item[i].pvalue);
154: break;
155: case DO_ENABLE:
156: value = true;
157: break;
158: case DO_DISABLE:
159: default:
160: value = false;
161: break;
162: }
163: *(item[i].pvalue) = value;
164: if (value) {
165: item[i].init();
166: } else {
167: item[i].uninit();
168: }
169: fprintf(stderr, "%s: %s\n", name, value?"ON":"OFF");
170: return true;
171: }
172: }
173: fprintf(stderr, "WARNING: unknown device '%s'\n\n", name);
174: fprintf(stderr, "Accepted devices are:\n");
175: for (i = 0; item[i].name; i++)
176: {
177: fprintf(stderr, "- %s\n", item[i].name);
178: }
179: return false;
180: }
181:
182: /*-----------------------------------------------------------------------*/
183: /**
184: * Parse path type name and set the path to given value.
185: * Return false if name parsing failed, true otherwise
186: */
187: static bool Control_SetPath(char *name)
188: {
189: struct {
190: const char *name;
191: char *path;
192: } item[] = {
193: { "memauto", ConfigureParams.Memory.szAutoSaveFileName },
194: { "memsave", ConfigureParams.Memory.szMemoryCaptureFileName },
195: { NULL, NULL }
196: };
197: int i;
198: char *arg;
199: const char *value;
200:
201: /* argument? */
202: arg = strchr(name, ' ');
203: if (arg) {
204: *arg = '\0';
205: value = Str_Trim(arg+1);
206: } else {
207: return false;
208: }
209:
210: for (i = 0; item[i].name; i++)
211: {
212: if (strcmp(name, item[i].name) == 0)
213: {
214: fprintf(stderr, "%s: %s -> %s\n", name, item[i].path, value);
215: strncpy(item[i].path, value, FILENAME_MAX-1);
216: return true;
217: }
218: }
219: fprintf(stderr, "WARNING: unknown path type '%s'\n\n", name);
220: fprintf(stderr, "Accepted paths types are:\n");
221: for (i = 0; item[i].name; i++)
222: {
223: fprintf(stderr, "- %s\n", item[i].name);
224: }
225: return false;
226: }
227:
228: /*-----------------------------------------------------------------------*/
229: /**
230: * Show Hatari remote usage info and return false
231: */
232: static bool Control_Usage(const char *cmd)
233: {
234: fprintf(stderr, "ERROR: unrecognized hatari command: '%s'", cmd);
235: fprintf(stderr,
236: "Supported commands are:\n"
237: "- hatari-debug <Debug UI command>\n"
238: "- hatari-event <event to simulate>\n"
239: "- hatari-option <command line options>\n"
240: "- hatari-enable/disable/toggle <device name>\n"
241: "- hatari-path <config name> <new path>\n"
242: "- hatari-shortcut <shortcut name>\n"
243: "- hatari-embed-info\n"
244: "- hatari-stop\n"
245: "- hatari-cont\n"
246: "The last two can be used to stop and continue the Hatari emulation.\n"
247: "All commands need to be separated by newlines.\n"
248: );
249: return false;
250: }
251:
252: /*-----------------------------------------------------------------------*/
253: /**
254: * Parse Hatari debug/event/option/toggle/path/shortcut command buffer.
255: * Given buffer is modified in-place.
256: */
257: void Control_ProcessBuffer(char *buffer)
258: {
259: char *cmd, *cmdend, *arg;
260: int ok = true;
261:
262: cmd = buffer;
263: do {
264: /* command terminator? */
265: cmdend = strchr(cmd, '\n');
266: if (cmdend) {
267: *cmdend = '\0';
268: }
269: /* arguments? */
270: arg = strchr(cmd, ' ');
271: if (arg) {
272: *arg = '\0';
273: arg = Str_Trim(arg+1);
274: }
275: if (arg) {
276: if (strcmp(cmd, "hatari-option") == 0) {
277: ok = Change_ApplyCommandline(arg);
278: } else if (strcmp(cmd, "hatari-debug") == 0) {
279: ok = DebugUI_RemoteParse(arg);
280: } else if (strcmp(cmd, "hatari-shortcut") == 0) {
281: ok = Shortcut_Invoke(arg);
282: } else if (strcmp(cmd, "hatari-event") == 0) {
283: ok = Control_InsertEvent(arg);
284: } else if (strcmp(cmd, "hatari-path") == 0) {
285: ok = Control_SetPath(arg);
286: } else if (strcmp(cmd, "hatari-enable") == 0) {
287: ok = Control_DeviceAction(arg, DO_ENABLE);
288: } else if (strcmp(cmd, "hatari-disable") == 0) {
289: ok = Control_DeviceAction(arg, DO_DISABLE);
290: } else if (strcmp(cmd, "hatari-toggle") == 0) {
291: ok = Control_DeviceAction(arg, DO_TOGGLE);
292: } else {
293: ok = Control_Usage(cmd);
294: }
295: } else {
296: if (strcmp(cmd, "hatari-embed-info") == 0) {
297: fprintf(stderr, "Embedded window ID change messages = ON\n");
298: bSendEmbedInfo = true;
299: } else if (strcmp(cmd, "hatari-stop") == 0) {
300: Main_PauseEmulation(true);
301: bRemotePaused = true;
302: } else if (strcmp(cmd, "hatari-cont") == 0) {
303: Main_UnPauseEmulation();
304: bRemotePaused = false;
305: } else {
306: ok = Control_Usage(cmd);
307: }
308: }
309: if (cmdend) {
310: cmd = cmdend + 1;
311: }
312: } while (ok && cmdend && *cmd);
313: }
314:
315:
316: #if HAVE_UNIX_DOMAIN_SOCKETS
317:
318: /* socket from which control command line options are read */
319: static int ControlSocket;
320:
321: /* pre-declared local functions */
322: static int Control_GetUISocket(void);
323:
324:
325: /*-----------------------------------------------------------------------*/
326: /**
327: * Check ControlSocket for new commands and execute them.
328: * Commands should be separated by newlines.
329: *
330: * Return true if remote pause ON (and connected), false otherwise
331: */
332: bool Control_CheckUpdates(void)
333: {
334: /* just using all trace options with +/- are about 300 chars */
335: char buffer[400];
336: struct timeval tv;
337: fd_set readfds;
338: ssize_t bytes;
339: int status, sock;
340:
341: /* socket of file? */
342: if (ControlSocket) {
343: sock = ControlSocket;
344: } else {
345: return false;
346: }
347:
348: /* ready for reading? */
349: tv.tv_usec = tv.tv_sec = 0;
350: do {
351: FD_ZERO(&readfds);
352: FD_SET(sock, &readfds);
353: if (bRemotePaused) {
354: /* return only when there're UI events
355: * (redraws etc) to save battery:
356: * http://bugzilla.libsdl.org/show_bug.cgi?id=323
357: */
358: int uisock = Control_GetUISocket();
359: if (uisock) {
360: FD_SET(uisock, &readfds);
361: if (uisock < sock) {
362: uisock = sock;
363: }
364: }
365: status = select(uisock+1, &readfds, NULL, NULL, NULL);
366: } else {
367: status = select(sock+1, &readfds, NULL, NULL, &tv);
368: }
369: if (status < 0) {
370: perror("Control socket select() error");
371: return false;
372: }
373: /* nothing to process here */
374: if (status == 0) {
375: return bRemotePaused;
376: }
377: if (!FD_ISSET(sock, &readfds)) {
378: return bRemotePaused;
379: }
380:
381: /* assume whole command can be read in one go */
382: bytes = read(sock, buffer, sizeof(buffer)-1);
383: if (bytes < 0)
384: {
385: perror("Control socket read");
386: return false;
387: }
388: if (bytes == 0) {
389: /* closed */
390: close(ControlSocket);
391: ControlSocket = 0;
392: return false;
393: }
394: buffer[bytes] = '\0';
395: Control_ProcessBuffer(buffer);
396:
397: } while (bRemotePaused);
398:
399: return false;
400: }
401:
402:
403: /*-----------------------------------------------------------------------*/
404: /**
405: * Open given control socket.
406: * Return NULL for success, otherwise an error string
407: */
408: const char *Control_SetSocket(const char *socketpath)
409: {
410: struct sockaddr_un address;
411: int newsock;
412:
413: newsock = socket(AF_UNIX, SOCK_STREAM, 0);
414: if (newsock < 0)
415: {
416: perror("socket creation");
417: return "Can't create AF_UNIX socket";
418: }
419:
420: address.sun_family = AF_UNIX;
421: strncpy(address.sun_path, socketpath, sizeof(address.sun_path));
422: address.sun_path[sizeof(address.sun_path)-1] = '\0';
423: Log_Printf(LOG_INFO, "Connecting to control socket '%s'...\n", address.sun_path);
424: if (connect(newsock, (struct sockaddr *)&address, sizeof(address)) < 0)
425: {
426: perror("socket connect");
427: close(newsock);
428: return "connection to control socket failed";
429: }
430:
431: if (ControlSocket) {
432: close(ControlSocket);
433: }
434: ControlSocket = newsock;
435: Log_Printf(LOG_INFO, "new control socket is '%s'\n", socketpath);
436: return NULL;
437: }
438:
439:
440: /*-----------------------------------------------------------------------
441: * Currently works only on X11.
442: *
443: * SDL_syswm.h automatically includes everything else needed.
444: */
445:
446: #if HAVE_SDL_SDL_CONFIG_H
447: #include <SDL_config.h>
448: #endif
449:
450: /* X11 available and SDL_config.h states that SDL supports X11 */
451: #if HAVE_X11 && SDL_VIDEO_DRIVER_X11
452: #include <SDL_syswm.h>
453:
454: /**
455: * Reparent Hatari window if so requested. Needs to be done inside
456: * Hatari because if SDL itself is requested to reparent itself,
457: * SDL window stops accepting any input (specifically done like
458: * this in SDL backends for some reason).
459: *
460: * 'noembed' argument tells whether the SDL window should be embedded
461: * or not.
462: *
463: * If the window is embedded (which means that SDL WM window needs
464: * to be hidden) when SDL is asked to fullscreen, Hatari window just
465: * disappears when returning back from fullscreen. I.e. call this
466: * with noembed=true _before_ fullscreening and any other time with
467: * noembed=false after changing window size. You can do this by
468: * giving bInFullscreen as the noembed value.
469: */
470: void Control_ReparentWindow(int width, int height, bool noembed)
471: {
472: Display *display;
473: Window parent_win, sdl_win, wm_win;
474: const char *parent_win_id;
475: SDL_SysWMinfo info;
476:
477: parent_win_id = getenv("PARENT_WIN_ID");
478: if (!parent_win_id) {
479: return;
480: }
481: parent_win = strtol(parent_win_id, NULL, 0);
482: if (!parent_win) {
483: Log_Printf(LOG_WARN, "Invalid PARENT_WIN_ID value '%s'\n", parent_win_id);
484: return;
485: }
486:
487: SDL_VERSION(&info.version);
488: if (!SDL_GetWMInfo(&info)) {
489: Log_Printf(LOG_WARN, "Failed to get SDL_GetWMInfo()\n");
490: return;
491: }
492: display = info.info.x11.display;
493: sdl_win = info.info.x11.window;
494: wm_win = info.info.x11.wmwindow;
495: info.info.x11.lock_func();
496: if (noembed) {
497: /* show WM window again */
498: XMapWindow(display, wm_win);
499: } else {
500: char buffer[12]; /* 32-bits in hex (+ '\r') + '\n' + '\0' */
501:
502: /* hide WM window for Hatari */
503: XUnmapWindow(display, wm_win);
504: /* reparent main Hatari window to given parent */
505: XReparentWindow(display, sdl_win, parent_win, 0, 0);
506:
507: /* whether to send new window size */
508: if (bSendEmbedInfo && ControlSocket) {
509: fprintf(stderr, "New %dx%d SDL window with ID: %lx\n",
510: width, height, sdl_win);
511: sprintf(buffer, "%dx%d", width, height);
512: if (write(ControlSocket, buffer, strlen(buffer)) < 0)
513: perror("Control_ReparentWindow write");
514: }
515: }
516: info.info.x11.unlock_func();
517: }
518:
519: /**
520: * Return the X connection socket or zero
521: */
522: static int Control_GetUISocket(void)
523: {
524: SDL_SysWMinfo info;
525: SDL_VERSION(&info.version);
526: if (!SDL_GetWMInfo(&info)) {
527: Log_Printf(LOG_WARN, "Failed to get SDL_GetWMInfo()\n");
528: return 0;
529: }
530: return ConnectionNumber(info.info.x11.display);
531: }
532:
533: #else /* HAVE_X11 */
534:
535: static int Control_GetUISocket(void)
536: {
537: return 0;
538: }
539: void Control_ReparentWindow(int width, int height, bool noembed)
540: {
541: /* TODO: implement the Windows part. SDL sources offer example */
542: Log_Printf(LOG_TODO, "Support for Hatari window reparenting not built in\n");
543: }
544:
545: #endif /* HAVE_X11 */
546:
547: #endif /* HAVE_UNIX_DOMAIN_SOCKETS */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.