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