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