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