Annotation of hatari/src/control.c, revision 1.1

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 */

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.