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