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