2023-03-25 22:20:45 +01:00
|
|
|
#include <SDL2/SDL.h>
|
|
|
|
#include <rfb/rfbclient.h>
|
2023-03-26 17:23:55 +02:00
|
|
|
#include <fxlink/devices.h>
|
|
|
|
#include <fxlink/logging.h>
|
2023-03-25 22:20:45 +01:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <assert.h>
|
2023-03-26 17:23:55 +02:00
|
|
|
#include <stdbool.h>
|
2023-03-25 22:20:45 +01:00
|
|
|
|
|
|
|
/* Application globals */
|
|
|
|
struct app {
|
2023-03-26 17:23:55 +02:00
|
|
|
/* RFB/VNC client to get framebuffers from VNC server */
|
2023-03-25 22:20:45 +01:00
|
|
|
rfbClient *client;
|
2023-03-26 17:23:55 +02:00
|
|
|
/* SDL globals used to store the framebuffer and show it on-screen */
|
2023-03-25 22:20:45 +01:00
|
|
|
SDL_Window *window;
|
|
|
|
SDL_Surface *surface;
|
2023-03-26 17:23:55 +02:00
|
|
|
/* CLI options */
|
|
|
|
bool display_calc;
|
|
|
|
bool display_sdl;
|
|
|
|
/* Calculator tracking */
|
|
|
|
libusb_context *libusb_ctx;
|
|
|
|
struct fxlink_device *calc;
|
2023-03-25 22:20:45 +01:00
|
|
|
};
|
|
|
|
static struct app app = { 0 };
|
|
|
|
|
|
|
|
static void cleanup(void)
|
|
|
|
{
|
|
|
|
if(app.client)
|
|
|
|
rfbClientCleanup(app.client);
|
|
|
|
if(app.window)
|
|
|
|
SDL_DestroyWindow(app.window);
|
|
|
|
SDL_Quit();
|
2023-03-26 17:23:55 +02:00
|
|
|
if(app.calc) {
|
|
|
|
fxlink_device_cleanup(app.calc);
|
|
|
|
free(app.calc);
|
|
|
|
}
|
|
|
|
if(app.libusb_ctx)
|
|
|
|
libusb_exit(app.libusb_ctx);
|
2023-03-25 22:20:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void fb_update(rfbClient *client)
|
|
|
|
{
|
2023-03-26 17:23:55 +02:00
|
|
|
if(app.display_sdl) {
|
|
|
|
SDL_UpdateWindowSurface(app.window);
|
|
|
|
}
|
|
|
|
if(app.display_calc && app.calc) {
|
|
|
|
uint16_t *fb16 = malloc(396 * 224 * 2);
|
|
|
|
uint8_t *buffer = (void *)app.surface->pixels;
|
|
|
|
|
|
|
|
for(int y = 0; y < app.surface->h; y++)
|
2023-03-25 22:20:45 +01:00
|
|
|
for(int x = 0; x < app.surface->w; x++) {
|
|
|
|
int offset = y * app.surface->pitch + 4 * x;
|
2023-03-26 17:23:55 +02:00
|
|
|
int R = buffer[offset + 2];
|
|
|
|
int G = buffer[offset + 1];
|
|
|
|
int B = buffer[offset + 0];
|
|
|
|
|
|
|
|
/* Conversion to RGB565 */
|
|
|
|
int c = ((R & 0xf8) << 8) | ((G & 0xfc) << 3) | ((B & 0xf8) >> 3);
|
|
|
|
fb16[396*y + x] = (c >> 8) | (c << 8);
|
2023-03-25 22:20:45 +01:00
|
|
|
}
|
2023-03-26 17:23:55 +02:00
|
|
|
|
|
|
|
fxlink_device_start_bulk_OUT(app.calc,
|
|
|
|
"cgvm", "fb", fb16, 396*224*2, true);
|
2023-03-25 22:20:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-26 17:23:55 +02:00
|
|
|
static int get_calculator(void)
|
2023-03-25 22:20:45 +01:00
|
|
|
{
|
2023-03-26 17:23:55 +02:00
|
|
|
int rc;
|
|
|
|
if((rc = libusb_init(&app.libusb_ctx)))
|
|
|
|
return elog_libusb(rc, "error initializing libusb");
|
|
|
|
|
|
|
|
libusb_set_option(app.libusb_ctx, LIBUSB_OPTION_LOG_LEVEL,
|
|
|
|
LIBUSB_LOG_LEVEL_WARNING);
|
|
|
|
fxlink_log_grab_libusb_logs();
|
|
|
|
|
|
|
|
delay_t d = delay_infinite();
|
|
|
|
app.calc = fxlink_device_find_wait(app.libusb_ctx, NULL, &d);
|
|
|
|
if(!app.calc)
|
|
|
|
return elog("no calculator found!\n");
|
|
|
|
|
|
|
|
/* Claim the fxlink interface */
|
|
|
|
if(!fxlink_device_claim_fxlink(app.calc)) {
|
|
|
|
fxlink_device_cleanup(app.calc);
|
|
|
|
free(app.calc);
|
|
|
|
app.calc = NULL;
|
|
|
|
return elog("could not claim fxlink interface on calculator!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
hlog("cgvm");
|
|
|
|
log_("connected to %s\n", fxlink_device_id(app.calc));
|
2023-03-26 18:02:34 +02:00
|
|
|
|
|
|
|
fxlink_device_start_bulk_IN(app.calc);
|
2023-03-26 17:23:55 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-03-26 18:02:34 +02:00
|
|
|
static rfbKeySym keycode_to_rfbKeySym(int keycode)
|
|
|
|
{
|
|
|
|
switch(keycode) {
|
|
|
|
case 0x91 /* F1 */: return XK_F1;
|
|
|
|
case 0x92 /* F2 */: return XK_F2;
|
|
|
|
case 0x93 /* F3 */: return XK_F3;
|
|
|
|
case 0x94 /* F4 */: return XK_F4;
|
|
|
|
case 0x95 /* F5 */: return XK_F5;
|
|
|
|
case 0x96 /* F6 */: return XK_F6;
|
|
|
|
|
|
|
|
case 0x81 /* SHIFT */: return XK_Shift_L;
|
|
|
|
case 0x82 /* OPTN */: return 0;
|
|
|
|
case 0x83 /* VARS */: return XK_Super_L;
|
|
|
|
case 0x84 /* MENU */: return 0;
|
|
|
|
case 0x85 /* LEFT */: return XK_Left;
|
|
|
|
case 0x86 /* UP */: return XK_Up;
|
|
|
|
|
|
|
|
case 0x71 /* ALPHA */: return XK_Control_L;
|
|
|
|
case 0x72 /* SQUARE */: return 0;
|
|
|
|
case 0x73 /* POWER */: return 0;
|
|
|
|
case 0x74 /* EXIT */: return 0;
|
|
|
|
case 0x75 /* DOWN */: return XK_Down;
|
|
|
|
case 0x76 /* RIGHT */: return XK_Right;
|
|
|
|
|
|
|
|
case 0x61 /* XOT */: return 'a';
|
|
|
|
case 0x62 /* LOG */: return 'b';
|
|
|
|
case 0x63 /* LN */: return 'c';
|
|
|
|
case 0x64 /* SIN */: return 'd';
|
|
|
|
case 0x65 /* COS */: return 'e';
|
|
|
|
case 0x66 /* TAN */: return 'f';
|
|
|
|
|
|
|
|
case 0x51 /* FRAC */: return 'g';
|
|
|
|
case 0x52 /* FD */: return 'h';
|
|
|
|
case 0x53 /* LEFTP */: return 'i';
|
|
|
|
case 0x54 /* RIGHTP */: return 'j';
|
|
|
|
case 0x55 /* COMMA */: return 'k';
|
|
|
|
case 0x56 /* ARROW */: return 'l';
|
|
|
|
|
|
|
|
case 0x41 /* 7 */: return 'm';
|
|
|
|
case 0x42 /* 8 */: return 'n';
|
|
|
|
case 0x43 /* 9 */: return 'o';
|
|
|
|
case 0x44 /* DEL */: return XK_BackSpace;
|
|
|
|
|
|
|
|
case 0x31 /* 4 */: return 'p';
|
|
|
|
case 0x32 /* 5 */: return 'q';
|
|
|
|
case 0x33 /* 6 */: return 'r';
|
|
|
|
case 0x34 /* MUL */: return 's';
|
|
|
|
case 0x35 /* DIV */: return 't';
|
|
|
|
|
|
|
|
case 0x21 /* 1 */: return 'u';
|
|
|
|
case 0x22 /* 2 */: return 'v';
|
|
|
|
case 0x23 /* 3 */: return 'w';
|
|
|
|
case 0x24 /* ADD */: return 'x';
|
|
|
|
case 0x25 /* SUB */: return 'y';
|
|
|
|
|
|
|
|
case 0x11 /* 0 */: return 'z';
|
|
|
|
case 0x12 /* DOT */: return ' ';
|
|
|
|
case 0x13 /* EXP */: return '"';
|
|
|
|
case 0x14 /* NEG */: return '-';
|
|
|
|
case 0x15 /* EXE */: return XK_Return;
|
|
|
|
|
|
|
|
default: return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void handle_calc_message(struct fxlink_message const *msg)
|
|
|
|
{
|
|
|
|
if(fxlink_message_is_apptype(msg, "cgvm", "pressed-keys")) {
|
|
|
|
uint8_t *keys = msg->data;
|
|
|
|
for(int i = 0; i < msg->size / 2; i++) {
|
|
|
|
int code = keycode_to_rfbKeySym(keys[2*i]);
|
|
|
|
int down = keys[2*i+1] ? TRUE : FALSE;
|
|
|
|
if(code > 0)
|
|
|
|
SendKeyEvent(app.client, code, down);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
hlog("cgvm");
|
|
|
|
log_("got unknown message: application '%.16s', type '%.16s'\n",
|
|
|
|
msg->application, msg->type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-26 17:23:55 +02:00
|
|
|
static void usage(int rc)
|
|
|
|
{
|
|
|
|
fprintf(stderr,
|
|
|
|
"usage: cgvm_vnc [--calc] [--sdl]\n"
|
|
|
|
"Connects to VNC server 127.0.0.1 and gets raw frames.\n"
|
|
|
|
"--calc: Send frames to a calculator (once one is detected).\n"
|
|
|
|
"--sdl: Show frames on an SDL window.\n");
|
|
|
|
exit(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
for(int i = 1; i < argc; i++) {
|
|
|
|
if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
|
|
|
|
usage(0);
|
|
|
|
else if(!strcmp(argv[i], "--calc"))
|
|
|
|
app.display_calc = true;
|
|
|
|
else if(!strcmp(argv[i], "--sdl"))
|
|
|
|
app.display_sdl = true;
|
|
|
|
else {
|
|
|
|
fprintf(stderr, "error: unrecognized option '%s'\n", argv[i]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(argc == 1)
|
|
|
|
usage(0);
|
|
|
|
if(!app.display_calc && !app.display_sdl)
|
|
|
|
usage(1);
|
|
|
|
|
2023-03-25 22:20:45 +01:00
|
|
|
atexit(cleanup);
|
|
|
|
signal(SIGINT, exit);
|
|
|
|
|
2023-03-26 17:23:55 +02:00
|
|
|
/* Create the SDL window regarless of whether display_sdl is set, since we
|
|
|
|
use its surface as RFB framebuffer */
|
|
|
|
SDL_Init(SDL_INIT_VIDEO);
|
2023-03-25 22:20:45 +01:00
|
|
|
app.window = SDL_CreateWindow("CG Virtual Monitor test VNC client",
|
|
|
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 396, 224, 0);
|
|
|
|
app.surface = SDL_GetWindowSurface(app.window);
|
2023-03-26 17:23:55 +02:00
|
|
|
assert(app.surface->format->BytesPerPixel == 4);
|
2023-03-25 22:20:45 +01:00
|
|
|
|
|
|
|
app.client = rfbGetClient(8, 3, 4);
|
|
|
|
app.client->FinishedFrameBufferUpdate = fb_update;
|
|
|
|
app.client->width = 396;
|
|
|
|
app.client->height = 224;
|
|
|
|
app.client->frameBuffer = app.surface->pixels;
|
|
|
|
|
|
|
|
app.client->format.bitsPerPixel=32;
|
|
|
|
app.client->format.redShift=app.surface->format->Rshift;
|
|
|
|
app.client->format.greenShift=app.surface->format->Gshift;
|
|
|
|
app.client->format.blueShift=app.surface->format->Bshift;
|
|
|
|
app.client->format.redMax=app.surface->format->Rmask>>app.client->format.redShift;
|
|
|
|
app.client->format.greenMax=app.surface->format->Gmask>>app.client->format.greenShift;
|
|
|
|
app.client->format.blueMax=app.surface->format->Bmask>>app.client->format.blueShift;
|
|
|
|
SetFormatAndEncodings(app.client);
|
|
|
|
|
2023-03-26 17:23:55 +02:00
|
|
|
int _argc = 4;
|
|
|
|
char *_argv[] = { "cgvm_vnc", "-encodings", "raw", "127.0.0.1", NULL };
|
|
|
|
if(!rfbInitClient(app.client, &_argc, _argv)) {
|
2023-03-25 22:20:45 +01:00
|
|
|
fprintf(stderr, "rfbInitClient failed\n");
|
|
|
|
app.client = NULL;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2023-03-26 17:23:55 +02:00
|
|
|
if(app.display_calc)
|
|
|
|
get_calculator();
|
|
|
|
|
2023-03-25 22:20:45 +01:00
|
|
|
while(1) {
|
|
|
|
SDL_Event e;
|
|
|
|
while(SDL_PollEvent(&e)) {
|
|
|
|
if(e.type == SDL_QUIT) {
|
|
|
|
fprintf(stderr, "SDL_QUIT: Exiting...\n");
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int i = WaitForMessage(app.client, 100);
|
|
|
|
if(i < 0) {
|
|
|
|
fprintf(stderr, "WaitForMessage() select: %d\n", i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if(i > 0 && !HandleRFBServerMessage(app.client)) {
|
|
|
|
fprintf(stderr, "HandleRFBServerMessage() failed\n");
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-26 17:23:55 +02:00
|
|
|
|
2023-03-26 18:02:34 +02:00
|
|
|
/* Run libusb's event loop */
|
2023-03-26 17:23:55 +02:00
|
|
|
struct timeval zero_tv = { 0 };
|
|
|
|
libusb_handle_events_timeout(app.libusb_ctx, &zero_tv);
|
2023-03-25 22:20:45 +01:00
|
|
|
|
2023-03-26 18:02:34 +02:00
|
|
|
/* Handle incoming transfers from the calc */
|
|
|
|
if(app.calc) {
|
|
|
|
struct fxlink_message *msg = fxlink_device_finish_bulk_IN(app.calc);
|
|
|
|
if(msg) {
|
|
|
|
handle_calc_message(msg);
|
|
|
|
fxlink_message_free(msg, true);
|
|
|
|
fxlink_device_start_bulk_IN(app.calc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-25 22:20:45 +01:00
|
|
|
return 0;
|
|
|
|
}
|