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