diff --git a/vnc-client/CMakeLists.txt b/vnc-client/CMakeLists.txt index d5bb8da..415c218 100644 --- a/vnc-client/CMakeLists.txt +++ b/vnc-client/CMakeLists.txt @@ -1,10 +1,19 @@ cmake_minimum_required(VERSION 3.15) project(CGVirtualMonitorVNCClient VERSION 1.0 LANGUAGES C) +# Usual install spot +list(APPEND CMAKE_MODULE_PATH "$ENV{HOME}/.local/lib/cmake") +# Environment-provided hint +if(DEFINED "$ENV{FXSDK_PATH}") + list(APPEND CMAKE_MODULE_PATH "$ENV{FXSDK_PATH}/lib/cmake") +endif() + find_package(LibVNCServer 0.9 REQUIRED) find_package(SDL2 2.0 REQUIRED) +find_package(LibFxlink 2.9 REQUIRED) add_executable(cgvm_vnc src/main.c) set_target_properties(cgvm_vnc PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") -target_link_libraries(cgvm_vnc LibVNCServer::vncclient SDL2::SDL2) +target_link_libraries(cgvm_vnc PRIVATE + LibVNCServer::vncclient SDL2::SDL2 LibFxlink::LibFxlink) diff --git a/vnc-client/src/main.c b/vnc-client/src/main.c index b0bba4e..3f1ade1 100644 --- a/vnc-client/src/main.c +++ b/vnc-client/src/main.c @@ -1,14 +1,25 @@ #include #include +#include +#include #include #include #include +#include /* Application globals */ struct app { + /* RFB/VNC client to get framebuffers from VNC server */ rfbClient *client; + /* SDL globals used to store the framebuffer and show it on-screen */ SDL_Window *window; SDL_Surface *surface; + /* CLI options */ + bool display_calc; + bool display_sdl; + /* Calculator tracking */ + libusb_context *libusb_ctx; + struct fxlink_device *calc; }; static struct app app = { 0 }; @@ -19,35 +30,107 @@ static void cleanup(void) if(app.window) SDL_DestroyWindow(app.window); SDL_Quit(); + if(app.calc) { + fxlink_device_cleanup(app.calc); + free(app.calc); + } + if(app.libusb_ctx) + libusb_exit(app.libusb_ctx); } static void fb_update(rfbClient *client) { -/* SDL_LockSurface(app.surface); - assert(app.surface->format->BytesPerPixel == 4); - uint8_t *buffer = (void *)app.surface->pixels; - for(int y = 0; y < app.surface->h; y++) { + 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++) for(int x = 0; x < app.surface->w; x++) { int offset = y * app.surface->pitch + 4 * x; - // R, G, B - buffer[offset + 2] = 255; - buffer[offset + 1] = 255; - buffer[offset + 0] = 0; + 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); } + + fxlink_device_start_bulk_OUT(app.calc, + "cgvm", "fb", fb16, 396*224*2, true); } - SDL_UnlockSurface(app.surface); */ - SDL_UpdateWindowSurface(app.window); } -int main(void) +static int get_calculator(void) { - SDL_Init(SDL_INIT_VIDEO); + 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)); + return 0; +} + +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); + atexit(cleanup); signal(SIGINT, exit); + /* Create the SDL window regarless of whether display_sdl is set, since we + use its surface as RFB framebuffer */ + SDL_Init(SDL_INIT_VIDEO); 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); + assert(app.surface->format->BytesPerPixel == 4); app.client = rfbGetClient(8, 3, 4); app.client->FinishedFrameBufferUpdate = fb_update; @@ -64,14 +147,17 @@ int main(void) app.client->format.blueMax=app.surface->format->Bmask>>app.client->format.blueShift; SetFormatAndEncodings(app.client); - int argc = 4; - char *argv[] = { "cgvm_vnc", "-encodings", "raw", "127.0.0.1", NULL }; - if(!rfbInitClient(app.client, &argc, argv)) { + int _argc = 4; + char *_argv[] = { "cgvm_vnc", "-encodings", "raw", "127.0.0.1", NULL }; + if(!rfbInitClient(app.client, &_argc, _argv)) { fprintf(stderr, "rfbInitClient failed\n"); app.client = NULL; return 1; } + if(app.display_calc) + get_calculator(); + while(1) { SDL_Event e; while(SDL_PollEvent(&e)) { @@ -90,6 +176,9 @@ int main(void) fprintf(stderr, "HandleRFBServerMessage() failed\n"); continue; } + + struct timeval zero_tv = { 0 }; + libusb_handle_events_timeout(app.libusb_ctx, &zero_tv); } return 0;