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