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