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