掌機 - Z-Pocket Game Pro (ZPG Pro) - vncviewer(SDL2)



參考資訊:
https://libvnc.github.io/doc/html/_s_d_lvncviewer_8c-example.html

main.c

#include <SDL.h>
#include <signal.h>
#include <rfb/rfbclient.h>

struct {
    int sdl;
    int rfb;
} buttonMapping[] = {
    { 1, rfbButton1Mask },
    { 2, rfbButton2Mask },
    { 3, rfbButton3Mask },
    { 4, rfbButton4Mask },
    { 5, rfbButton5Mask },
    { 0, 0 }
};

struct {
    char mask;
    int bits_stored;
} utf8Mapping[] = {
    { 0b00111111, 6 },
    { 0b01111111, 7 },
    { 0b00011111, 5 },
    { 0b00001111, 4 },
    { 0b00000111, 3 },
    { 0, 0 }
};

int sdlFlags;
SDL_Window *sdlWindow;
SDL_Texture *sdlTexture;
SDL_Renderer *sdlRenderer;
static int enableResizable = 1, viewOnly, listenLoop, buttonMask;

int x, y;
static int rightAltKeyDown, leftAltKeyDown;

static rfbBool resize(rfbClient *client)
{
    int width = client->width, height = client->height, depth = client->format.bitsPerPixel;

    if (enableResizable) {
        sdlFlags |= SDL_WINDOW_RESIZABLE;
    }
    client->updateRect.x = client->updateRect.y = 0;
    client->updateRect.w = width;
    client->updateRect.h = height;

    SDL_FreeSurface(rfbClientGetClientData(client, SDL_Init));
    SDL_Surface *sdl = SDL_CreateRGBSurface(0, width, height, depth, 0, 0, 0, 0);
    if (!sdl) {
        rfbClientErr("resize: error creating surface: %s\n", SDL_GetError());
    }
    rfbClientSetClientData(client, SDL_Init, sdl);
    client->width = sdl->pitch / (depth / 8);
    client->frameBuffer = sdl->pixels;
    client->format.bitsPerPixel = depth;
    client->format.redShift = sdl->format->Rshift;
    client->format.greenShift = sdl->format->Gshift;
    client->format.blueShift = sdl->format->Bshift;
    client->format.redMax = sdl->format->Rmask >> client->format.redShift;
    client->format.greenMax = sdl->format->Gmask >> client->format.greenShift;
    client->format.blueMax = sdl->format->Bmask >> client->format.blueShift;
    SetFormatAndEncodings(client);
    
    if (!sdlWindow) {
        sdlWindow = SDL_CreateWindow(client->desktopName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, sdlFlags);
        if (!sdlWindow) {
            rfbClientErr("resize: error creating window: %s\n", SDL_GetError());
        }
    }
    else {
        SDL_SetWindowSize(sdlWindow, width, height);
    }
    if (!sdlRenderer) {
        sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, 0);
        if (!sdlRenderer) {
            rfbClientErr("resize: error creating renderer: %s\n", SDL_GetError());
        }
        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
    }
    SDL_RenderSetLogicalSize(sdlRenderer, width, height);
    if (sdlTexture) {
        SDL_DestroyTexture(sdlTexture);
    }
    sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
    if (!sdlTexture) {
        rfbClientErr("resize: error creating texture: %s\n", SDL_GetError());
    }
    return TRUE;
}

static rfbKeySym SDL_key2rfbKeySym(SDL_KeyboardEvent *e)
{
    rfbKeySym k = 0;
    SDL_Keycode sym = e->keysym.sym;

    switch(sym) {
    case SDLK_BACKSPACE:
        k = XK_BackSpace;
        break;
    case SDLK_TAB:
        k = XK_Tab;
        break;
    case SDLK_CLEAR:
        k = XK_Clear;
        break;
    case SDLK_RETURN:
        k = XK_Return;
        break;
    case SDLK_PAUSE:
        k = XK_Pause;
        break;
    case SDLK_ESCAPE:
        k = XK_Escape;
        break;
    case SDLK_DELETE:
        k = XK_Delete;
        break;
    case SDLK_KP_0:
        k = XK_KP_0;
        break;
    case SDLK_KP_1:
        k = XK_KP_1;
        break;
    case SDLK_KP_2:
        k = XK_KP_2;
        break;
    case SDLK_KP_3:
        k = XK_KP_3;
        break;
    case SDLK_KP_4:
        k = XK_KP_4;
        break;
    case SDLK_KP_5:
        k = XK_KP_5;
        break;
    case SDLK_KP_6:
        k = XK_KP_6;
        break;
    case SDLK_KP_7:
        k = XK_KP_7;
        break;
    case SDLK_KP_8:
        k = XK_KP_8;
        break;
    case SDLK_KP_9:
        k = XK_KP_9;
        break;
    case SDLK_KP_PERIOD:
        k = XK_KP_Decimal;
        break;
    case SDLK_KP_DIVIDE:
        k = XK_KP_Divide;
        break;
    case SDLK_KP_MULTIPLY:
        k = XK_KP_Multiply;
        break;
    case SDLK_KP_MINUS:
        k = XK_KP_Subtract;
        break;
    case SDLK_KP_PLUS:
        k = XK_KP_Add;
        break;
    case SDLK_KP_ENTER:
        k = XK_KP_Enter;
        break;
    case SDLK_KP_EQUALS:
        k = XK_KP_Equal;
        break;
    case SDLK_UP:
        k = XK_Up;
        break;
    case SDLK_DOWN:
        k = XK_Down;
        break;
    case SDLK_RIGHT:
        k = XK_Right;
        break;
    case SDLK_LEFT:
        k = XK_Left;
        break;
    case SDLK_INSERT:
        k = XK_Insert;
        break;
    case SDLK_HOME:
        k = XK_Home;
        break;
    case SDLK_END:
        k = XK_End;
        break;
    case SDLK_PAGEUP:
        k = XK_Page_Up;
        break;
    case SDLK_PAGEDOWN:
        k = XK_Page_Down;
        break;
    case SDLK_F1:
        k = XK_F1;
        break;
    case SDLK_F2:
        k = XK_F2;
        break;
    case SDLK_F3:
        k = XK_F3;
        break;
    case SDLK_F4:
        k = XK_F4;
        break;
    case SDLK_F5:
        k = XK_F5;
        break;
    case SDLK_F6:
        k = XK_F6;
        break;
    case SDLK_F7:
        k = XK_F7;
        break;
    case SDLK_F8:
        k = XK_F8;
        break;
    case SDLK_F9:
        k = XK_F9;
        break;
    case SDLK_F10:
        k = XK_F10;
        break;
    case SDLK_F11:
        k = XK_F11;
        break;
    case SDLK_F12:
        k = XK_F12;
        break;
    case SDLK_F13:
        k = XK_F13;
        break;
    case SDLK_F14:
        k = XK_F14;
        break;
    case SDLK_F15:
        k = XK_F15;
        break;
    case SDLK_NUMLOCKCLEAR:
        k = XK_Num_Lock;
        break;
    case SDLK_CAPSLOCK:
        k = XK_Caps_Lock;
        break;
    case SDLK_SCROLLLOCK:
        k = XK_Scroll_Lock;
        break;
    case SDLK_RSHIFT:
        k = XK_Shift_R;
        break;
    case SDLK_LSHIFT:
        k = XK_Shift_L;
        break;
    case SDLK_RCTRL:
        k = XK_Control_R;
        break;
    case SDLK_LCTRL:
        k = XK_Control_L;
        break;
    case SDLK_RALT:
        k = XK_Alt_R;
        break;
    case SDLK_LALT:
        k = XK_Alt_L;
        break;
    case SDLK_LGUI:
        k = XK_Super_L;
        break;
    case SDLK_RGUI:
        k = XK_Super_R;
        break;
#if 0
    case SDLK_COMPOSE:
        k = XK_Compose;
        break;
#endif
    case SDLK_MODE:
        k = XK_Mode_switch;
        break;
    case SDLK_HELP:
        k = XK_Help;
        break;
    case SDLK_PRINTSCREEN:
        k = XK_Print;
        break;
    case SDLK_SYSREQ:
        k = XK_Sys_Req;
        break;
    default:
        break;
    }
    if (k == 0 && sym > 0x0 && sym < 0x100 && e->keysym.mod & KMOD_CTRL) {
        k = sym;
    }
    return k;
}

static rfbKeySym utf8char2rfbKeySym(const char chr[4])
{
    int i;
    int bytes = strlen(chr);
    int shift = utf8Mapping[0].bits_stored * (bytes - 1);
    rfbKeySym codep = (*chr++ & utf8Mapping[bytes].mask) << shift;

    for (i = 1; i < bytes; ++i, ++chr) {
        shift -= utf8Mapping[0].bits_stored;
        codep |= ((char) * chr & utf8Mapping[0].mask) << shift;
    }
    return codep;
}

static void update(rfbClient *cl, int x, int y, int w, int h)
{
    SDL_Rect r = { x, y, w, h };
    SDL_Surface *sdl = rfbClientGetClientData(cl, SDL_Init);
    
    if (SDL_UpdateTexture(sdlTexture, &r, sdl->pixels + y * sdl->pitch + x * 4, sdl->pitch) < 0) {
        rfbClientErr("update: failed to update texture: %s\n", SDL_GetError());
    }
    if (SDL_RenderClear(sdlRenderer) < 0) {
        rfbClientErr("update: failed to clear renderer: %s\n", SDL_GetError());
    }
    if (SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL) < 0) {
        rfbClientErr("update: failed to copy texture to renderer: %s\n", SDL_GetError());
    }
    SDL_RenderPresent(sdlRenderer);
}

static void kbd_leds(rfbClient *cl, int value, int pad)
{
    fprintf(stderr, "Led State= 0x%02X\n", value);
    fflush(stderr);
}

static void text_chat(rfbClient *cl, int value, char *text)
{
    switch(value) {
    case rfbTextChatOpen:
        fprintf(stderr, "TextChat: We should open a textchat window!\n");
        TextChatOpen(cl);
        break;
    case rfbTextChatClose:
        fprintf(stderr, "TextChat: We should close our window!\n");
        break;
    case rfbTextChatFinished:
        fprintf(stderr, "TextChat: We should close our window!\n");
        break;
    default:
        fprintf(stderr, "TextChat: Received \"%s\"\n", text);
        break;
    }
    fflush(stderr);
}

#ifdef __MINGW32__
    #define LOG_TO_FILE
#endif

#ifdef LOG_TO_FILE
#include <stdarg.h>
static void log_to_file(const char *format, ...)
{
    va_list args;
    FILE *logfile;
    char buf[256];
    time_t log_clock;
    static char *logfile_str = 0;

    if (!rfbEnableClientLogging) {
        return;
    }
    if (logfile_str == 0) {
        logfile_str = getenv("VNCLOG");
        if (logfile_str == 0) {
            logfile_str = "vnc.log";
        }
    }
    logfile = fopen(logfile_str, "a");
    va_start(args, format);
    time(&log_clock);
    strftime(buf, 255, "%d/%m/%Y %X ", localtime(&log_clock));
    fprintf(logfile, buf);
    vfprintf(logfile, format, args);
    fflush(logfile);
    va_end(args);
    fclose(logfile);
}
#endif

static void cleanup(rfbClient *cl)
{
    SDL_QuitSubSystem(SDL_INIT_VIDEO);
    SDL_InitSubSystem(SDL_INIT_VIDEO);
    if (cl) {
        rfbClientCleanup(cl);
    }
}

static rfbBool handleSDLEvent(rfbClient *cl, SDL_Event *e)
{
    switch (e->type) {
    case SDL_WINDOWEVENT:
        switch(e->window.event) {
        case SDL_WINDOWEVENT_EXPOSED:
            SendFramebufferUpdateRequest(cl, 0, 0, cl->width, cl->height, FALSE);
            break;
        case SDL_WINDOWEVENT_FOCUS_GAINED:
            if (SDL_HasClipboardText()) {
                char *text = SDL_GetClipboardText();
                if (text) {
                    rfbClientLog("sending clipboard text '%s'\n", text);
                    SendClientCutText(cl, text, strlen(text));
                }
            }
            break;
        case SDL_WINDOWEVENT_FOCUS_LOST:
            if (rightAltKeyDown) {
                SendKeyEvent(cl, XK_Alt_R, FALSE);
                rightAltKeyDown = FALSE;
                rfbClientLog("released right Alt key\n");
            }
            if (leftAltKeyDown) {
                SendKeyEvent(cl, XK_Alt_L, FALSE);
                leftAltKeyDown = FALSE;
                rfbClientLog("released left Alt key\n");
            }
            break;
        }
        break;
    case SDL_MOUSEWHEEL: {
        int steps;
        if (viewOnly) {
            break;
        }
        if (e->wheel.y > 0)
            for (steps = 0; steps < e->wheel.y; ++steps) {
                SendPointerEvent(cl, x, y, rfbButton4Mask);
                SendPointerEvent(cl, x, y, 0);
            }
        if (e->wheel.y < 0)
            for (steps = 0; steps > e->wheel.y; --steps) {
                SendPointerEvent(cl, x, y, rfbButton5Mask);
                SendPointerEvent(cl, x, y, 0);
            }
        if (e->wheel.x > 0)
            for (steps = 0; steps < e->wheel.x; ++steps) {
                SendPointerEvent(cl, x, y, 0b01000000);
                SendPointerEvent(cl, x, y, 0);
            }
        if (e->wheel.x < 0)
            for (steps = 0; steps > e->wheel.x; --steps) {
                SendPointerEvent(cl, x, y, 0b00100000);
                SendPointerEvent(cl, x, y, 0);
            }
        break;
    }
    case SDL_MOUSEBUTTONUP:
    case SDL_MOUSEBUTTONDOWN:
    case SDL_MOUSEMOTION: {
        int state, i;
        if (viewOnly) {
            break;
        }
        if (e->type == SDL_MOUSEMOTION) {
            x = e->motion.x;
            y = e->motion.y;
            state = e->motion.state;
        }
        else {
            x = e->button.x;
            y = e->button.y;
            state = e->button.button;
            for (i = 0; buttonMapping[i].sdl; i++)
                if (state == buttonMapping[i].sdl) {
                    state = buttonMapping[i].rfb;
                    if (e->type == SDL_MOUSEBUTTONDOWN) {
                        buttonMask |= state;
                    }
                    else {
                        buttonMask &= ~state;
                    }
                    break;
                }
        }
        SendPointerEvent(cl, x, y, buttonMask);
        buttonMask &= ~(rfbButton4Mask | rfbButton5Mask);
        break;
    }
    case SDL_KEYUP:
    case SDL_KEYDOWN:
        if (viewOnly) {
            break;
        }
        SendKeyEvent(cl, SDL_key2rfbKeySym(&e->key), e->type == SDL_KEYDOWN ? TRUE : FALSE);
        if (e->key.keysym.sym == SDLK_RALT) {
            rightAltKeyDown = e->type == SDL_KEYDOWN;
        }
        if (e->key.keysym.sym == SDLK_LALT) {
            leftAltKeyDown = e->type == SDL_KEYDOWN;
        }
        break;
    case SDL_TEXTINPUT:
        if (viewOnly) {
            break;
        }
        rfbKeySym sym = utf8char2rfbKeySym(e->text.text);
        SendKeyEvent(cl, sym, TRUE);
        SendKeyEvent(cl, sym, FALSE);
        break;
    case SDL_JOYBUTTONUP:
    case SDL_JOYBUTTONDOWN:
        if (viewOnly) {
            break;
        }
        {
            //zpg pro
            uint32_t myremap[] = {
                XK_Alt_L, XK_Control_L, XK_space, XK_Shift_L, XK_Tab, XK_BackSpace, XK_Page_Up, XK_Page_Down, 
                XK_Up, XK_Down, XK_Left, XK_Right, XK_Home, XK_End, XK_Escape, XK_Return, XK_F1, XK_F2
            };
            SendKeyEvent(cl, myremap[e->jbutton.button], e->type == SDL_JOYBUTTONDOWN ? TRUE : FALSE);
        }
        break;
    case SDL_QUIT:
        if (listenLoop) {
            cleanup(cl);
            return FALSE;
        }
        else {
            rfbClientCleanup(cl);
            exit(0);
        }
    default:
        //rfbClientLog("ignore SDL event: 0x%x\n", e->type);
        break;
    }
    return TRUE;
}

static void got_selection(rfbClient *cl, const char *text, int len)
{
    rfbClientLog("received clipboard text '%s'\n", text);
    if (SDL_SetClipboardText(text) != 0) {
        rfbClientErr("could not set received clipboard text: %s\n", SDL_GetError());
    }
}

static rfbCredential *get_credential(rfbClient *cl, int credentialType)
{
    rfbCredential *c = malloc(sizeof(rfbCredential));
    c->userCredential.username = malloc(RFB_BUF_SIZE);
    c->userCredential.password = malloc(RFB_BUF_SIZE);
    if (credentialType != rfbCredentialTypeUser) {
        rfbClientErr("something else than username and password required for authentication\n");
        return NULL;
    }
    rfbClientLog("username and password required for authentication!\n");
    printf("user: ");
    fgets(c->userCredential.username, RFB_BUF_SIZE, stdin);
    printf("pass: ");
    fgets(c->userCredential.password, RFB_BUF_SIZE, stdin);
    c->userCredential.username[strcspn(c->userCredential.username, "\n")] = 0;
    c->userCredential.password[strcspn(c->userCredential.password, "\n")] = 0;
    return c;
}

#ifdef mac
    #define main SDLmain
#endif
int main(int argc, char **argv)
{
    int i, j;
    SDL_Event e;
    rfbClient *cl;
#ifdef LOG_TO_FILE
    rfbClientLog = rfbClientErr = log_to_file;
#endif
    
    for (i = 1, j = 1; i < argc; i++) {
        if (!strcmp(argv[i], "-viewonly")) {
            viewOnly = 1;
        }
        else if (!strcmp(argv[i], "-resizable")) {
            enableResizable = 1;
        }
        else if (!strcmp(argv[i], "-no-resizable")) {
            enableResizable = 0;
        }
        else if (!strcmp(argv[i], "-listen")) {
            listenLoop = 1;
            argv[i] = "-listennofork";
            ++j;
        }
        else {
            if (i != j) {
                argv[j] = argv[i];
            }
            j++;
        }
    }

    argc = j;
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK);
    SDL_JoystickEventState(SDL_ENABLE);
    SDL_JoystickOpen(0);

    atexit(SDL_Quit);
    signal(SIGINT, exit);
    do {
        cl = rfbGetClient(8, 3, 4);
        cl->MallocFrameBuffer = resize;
        cl->canHandleNewFBSize = TRUE;
        cl->GotFrameBufferUpdate = update;
        cl->HandleKeyboardLedState = kbd_leds;
        cl->HandleTextChat = text_chat;
        cl->GotXCutText = got_selection;
        cl->GetCredential = get_credential;
        cl->listenPort = LISTEN_PORT_OFFSET;
        cl->listen6Port = LISTEN_PORT_OFFSET;
        if (!rfbInitClient(cl, &argc, argv)) {
            cl = NULL;
            cleanup(cl);
            break;
        }
        while (1) {
            if (SDL_PollEvent(&e)) {
                if (!handleSDLEvent(cl, &e)) {
                    break;
                }
            }
            else {
                i = WaitForMessage(cl, 500);
                if (i < 0) {
                    cleanup(cl);
                    break;
                }
                if (i) {
                    if (!HandleRFBServerMessage(cl)) {
                        cleanup(cl);
                        break;
                    }
                }
            }
        }
    }
    while (listenLoop);
    return 0;
}

編譯

$ cd
$ wget https://github.com/steward-fu/website/releases/download/zpg-pro/toolchain-x64.tar.gz
$ tar xvf toolchain-x64.tar.gz
$ sudo mv gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu /opt/zpg-sdk
$ export PATH=$PATH:/opt/zpg-sdk/bin

$ aarch64-none-linux-gnu-gcc main.c -o vnc -lSDL2 -lvncclient -I/opt/zpg-sdk/aarch64-none-linux-gnu/include/SDL2