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