Compare commits

...

10 Commits

Author SHA1 Message Date
Lephenixnoir a3864fad21
addin: proper icon + screenshot 2023-03-26 23:02:30 +02:00
Lephenixnoir 94cfbee2e6
vnc: double monitor setup 2023-03-26 22:34:16 +02:00
Lephenixnoir 9319f162bc
vnc: decouple monitor data from app, for no particular reason
I have some suspicion MONITOR_COUNT betrayed me.
2023-03-26 22:13:17 +02:00
Lephenixnoir 74a7f05e9d
vnc: structure improvements, don't use SDL unless requested 2023-03-26 21:40:11 +02:00
Lephenixnoir b6c782666c
vnc: fix incorrect calculator disconnect detection condition 2023-03-26 21:08:39 +02:00
Lephenixnoir fb9174197f
addin: improve stability by not blocking on reads 2023-03-26 21:03:30 +02:00
Lephenixnoir 9e76db9e7e
vnc: improve robustsness by supporting hotplugging 2023-03-26 18:30:38 +02:00
Lephenixnoir c0b1ced527
vnc addin: allow calculator to send inputs to VNC server 2023-03-26 18:02:34 +02:00
Lephenixnoir 1be08a29df
addin: get and show the frames, basic version (~30 FPS)
Performance is very good with debug disabled. USB transmission takes
14 ms of reading + 6 ms of waiting each frame (double buffer mode is not
supported by gint yet). Adding 11 ms of display update makes it about
30 FPS, which is nice!
2023-03-26 17:28:16 +02:00
Lephenixnoir b39ccf9758
vnc: usb libfxlink to grab a calculator and stream it the frames
My use of libfxlink is terrible though, doesn't handle disconnects etc.
Will improve soon.
2023-03-26 17:27:59 +02:00
9 changed files with 575 additions and 68 deletions

View File

@ -1,16 +1,36 @@
Setting up the extra display and starting wayvnc
Setting up the extra display using sway's hidden [`create_output`](https://github.com/swaywm/sway/blob/master/sway/commands/create_output.c) command, and starting wayvnc
```
% swaymsg create_output
% swaymsg output "HEADLESS-1" resolution 396x224
% wayvnc -o HEADLESS-1
# to remove the output later
% swaymsg output "HEADLESS-1" unplug
```
Compiling and starting the VNC client
```
% cd vnc-client
% cmake -B build
% cmake -B build # options...
% make -C build
% ./cgvm_vnc
% ./cgvm_vnc --calc
```
If you installed native fxSDK tools outside of the default `$HOME/.local`, add `-DFXSDK_PATH=<the path>` to CMake options. (GiteaPC uses the default path.)
Compiling and installing the virtual monitor add-in
```
% cd cgvm-addin
% fxsdk build-cg -s
```
---
This entire thing was coded in a few hours with limited regard for exceptions. Ideally, you should
1. Open the Virtual Monitor add-in on the calculator
2. Start `cgvm_vnc`
3. Not disconnect the calculator while `cgvm_vnc` is running
4. After closing `cgvm_vnc`, disconnect the calculator

View File

@ -8,6 +8,7 @@ include(GenerateG1A)
include(GenerateG3A)
include(Fxconv)
find_package(Gint 2.9 REQUIRED)
find_package(LibProf 2.4 REQUIRED)
set(SOURCES
src/main.c
@ -27,12 +28,12 @@ fxconv_declare_assets(${ASSETS} ${ASSETS_fx} ${ASSETS_cg} WITH_METADATA)
add_executable(myaddin ${SOURCES} ${ASSETS} ${ASSETS_${FXSDK_PLATFORM}})
target_compile_options(myaddin PRIVATE -Wall -Wextra -Os)
target_link_libraries(myaddin Gint::Gint)
target_link_libraries(myaddin LibProf::LibProf Gint::Gint)
if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G)
generate_g1a(TARGET myaddin OUTPUT "FXVirtMo.g1a"
NAME "FXVirtMo" ICON assets-fx/icon.png)
elseif("${FXSDK_PLATFORM_LONG}" STREQUAL fxCG50)
generate_g3a(TARGET myaddin OUTPUT "CGVirtMo.g3a"
NAME "Virtual Monitor" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png)
NAME "Virt Monitor" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png)
endif()

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,12 +1,148 @@
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
#include <gint/gint.h>
#include <libprof.h>
#include <fxlibc/printf.h>
/* Debug mode (uses gint perf counters not committed to repository) */
// #define DEBUG
//---
// Receiving and displaying video frames
//---
static void process_usb_message(struct usb_fxlink_header const *header)
{
static int count = 0;
count++;
if(header->size > DWIDTH * DHEIGHT * 2) {
dclear(C_BLACK);
dprint(1, 1, C_WHITE, "[%d] %.16s %.16s (%d B)", count,
header->application, header->type, header->size);
dupdate();
return;
}
#ifdef DEBUG
extern prof_t prof_round, prof_core;
prof_round = prof_make();
prof_core = prof_make();
prof_t prof_read = prof_make();
prof_enter(prof_read);
#endif
/* Read the message payload directly into VRAM. We set a timeout so the
calculator doesn't freeze if the communication stops midway because the
VNC client on the PC was interrupted. */
timeout_t tm = timeout_make_ms(250);
usb_read_sync_timeout(usb_ff_bulk_input(), gint_vram, header->size, false,
&tm);
#ifdef DEBUG
prof_leave(prof_read);
dprint_opt(1, 1, C_WHITE, C_BLACK, DTEXT_LEFT, DTEXT_TOP,
"%d ms (read %.1D ms, wait %.1D ms)\n",
prof_time(prof_read) / 1000,
prof_time(prof_round) / 100,
prof_time(prof_core) / 100);
#endif
dupdate();
}
//---
// Sending keyboard events
//---
#define MAX_EVENTS_PER_MESSAGE 16
struct key_event { uint8_t key; bool down; };
/* Keyboard data to be sent to the PC whenever a key is pressed */
static struct key_event keyinfo[MAX_EVENTS_PER_MESSAGE];
/* Whether there is such a transfer in progress */
static bool keyinfo_frame = false;
static void send_keyboard_events_callback(void)
{
keyinfo_frame = false;
}
static void send_keyboard_events(void)
{
int i = 0;
key_event_t ev;
while((ev = pollevent()).type != KEYEV_NONE) {
/* Filter out repeats and any events past the maximum number */
if(ev.type != KEYEV_DOWN && ev.type != KEYEV_UP)
continue;
if(i >= MAX_EVENTS_PER_MESSAGE)
continue;
keyinfo[i].key = ev.key;
keyinfo[i].down = (ev.type == KEYEV_DOWN);
i++;
}
/* Send keyboard updates via fxlink */
if(i > 0 && usb_is_open() && !keyinfo_frame) {
usb_fxlink_header_t header;
usb_fxlink_fill_header(&header, "cgvm", "pressed-keys", 2*i);
/* We don't need to use async writes because all of this clearly fits
in a single buffer (2 kB) and only the commit communicates */
int pipe = usb_ff_bulk_output();
usb_write_sync(pipe, &header, sizeof header, false);
usb_write_sync(pipe, keyinfo, i * sizeof *keyinfo, false);
usb_commit_async(pipe, GINT_CALL(send_keyboard_events_callback));
/* Remember that a transfer is in progress */
keyinfo_frame = true;
}
}
//--
// Main loop
//---
int main(void)
{
dclear(C_WHITE);
dtext(1, 1, C_BLACK, "Sample fxSDK add-in.");
dupdate();
#ifdef DEBUG
prof_init();
__printf_enable_fixed();
#endif
getkey();
return 1;
dclear(C_WHITE);
dprint(1, 1, C_BLACK, "Opening USB connection...");
dupdate();
usb_interface_t const *intf[] = { &usb_ff_bulk, NULL };
usb_open(intf, GINT_CALL_NULL);
usb_open_wait();
dclear(C_WHITE);
dprint(1, 1, C_BLACK, "USB connected!");
dupdate();
struct usb_fxlink_header header;
while(1) {
send_keyboard_events();
if(keydown(KEY_MENU))
gint_osmenu();
if(keydown(KEY_EXIT))
break;
while(usb_fxlink_handle_messages(&header)) {
process_usb_message(&header);
}
}
#ifdef DEBUG
prof_quit();
#endif
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

View File

@ -1,10 +1,20 @@
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)
target_compile_options(cgvm_vnc PRIVATE -g)
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)

View File

@ -1,94 +1,434 @@
#include <SDL2/SDL.h>
#include <rfb/rfbclient.h>
#include <fxlink/devices.h>
#include <fxlink/logging.h>
#include <stdio.h>
#include <signal.h>
#include <assert.h>
#include <stdbool.h>
/* Size of the calculator display */
#define CALC_WIDTH 396
#define CALC_HEIGHT 224
/* Number of monitors */
#define MONITOR_COUNT 2
/* Tracking data for a calculator-attached virtual monitor */
struct monitor {
/* 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;
/* Calculator */
struct fxlink_device *calc;
libusb_device *calc_unique_id;
/* 16-bit framebuffer for the calculator (big endian) */
uint16_t *fb16_be;
};
/* Application globals */
struct app {
rfbClient *client;
SDL_Window *window;
SDL_Surface *surface;
/* CLI options */
bool display_calc;
bool display_sdl;
/* Calculator tracking */
libusb_context *libusb_ctx;
struct fxlink_device_list devices;
/* All monitors */
struct monitor *monitors[MONITOR_COUNT];
};
static struct app app = { 0 };
/* Cleanup all resources when execution finishes. */
static void cleanup(void)
{
if(app.client)
rfbClientCleanup(app.client);
if(app.window)
SDL_DestroyWindow(app.window);
/* Wait for libusb to settle down */
while(fxlink_device_list_interrupt(&app.devices))
libusb_handle_events(app.libusb_ctx);
for(int i = 0; i < MONITOR_COUNT; i++) {
struct monitor *mon = app.monitors[i];
if(!mon)
continue;
if(mon->client)
rfbClientCleanup(mon->client);
if(mon->window)
SDL_DestroyWindow(mon->window);
/* This device is managed by the device list */
mon->calc = NULL;
mon->calc_unique_id = NULL;
free(mon->fb16_be);
free(mon);
}
SDL_Quit();
fxlink_device_list_stop(&app.devices);
if(app.libusb_ctx)
libusb_exit(app.libusb_ctx);
}
/* Handle a framebuffer update by displaying to the SDL window and/or sending a
new 16-bit framebuffer to the calculator. */
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++) {
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;
}
uint32_t *fb = (void *)client->frameBuffer;
struct monitor *mon = rfbClientGetClientData(client, NULL);
if(app.display_sdl) {
/* Very crude assumption about the SDL surface format and pitch */
memcpy(mon->surface->pixels, fb, CALC_WIDTH * CALC_HEIGHT * 4);
SDL_UpdateWindowSurface(mon->window);
}
if(app.display_calc && mon->calc && mon->fb16_be) {
for(int y = 0; y < CALC_HEIGHT; y++)
for(int x = 0; x < CALC_WIDTH; x++) {
uint32_t color = fb[CALC_WIDTH * y + x];
int R = (color >> 16) & 0xff;
int G = (color >> 8) & 0xff;
int B = (color & 0xff);
/* Conversion to RGB565 */
int c = ((R & 0xf8) << 8) | ((G & 0xfc) << 3) | ((B & 0xf8) >> 3);
mon->fb16_be[CALC_WIDTH * y + x] = (c >> 8) | (c << 8);
}
fxlink_device_start_bulk_OUT(mon->calc,
"cgvm", "fb", mon->fb16_be, CALC_WIDTH * CALC_HEIGHT * 2, false);
}
SDL_UnlockSurface(app.surface); */
SDL_UpdateWindowSurface(app.window);
}
int main(void)
/* Convert a gint keycode to an rfbKeySym. This is just for show, the keymap is
way too basic to be useful. */
static rfbKeySym keycode_to_rfbKeySym(int keycode)
{
SDL_Init(SDL_INIT_VIDEO);
atexit(cleanup);
signal(SIGINT, exit);
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;
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);
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;
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;
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;
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);
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;
}
}
/* Handle incoming messages from the calculator. */
static void handle_calc_message(struct fxlink_message const *msg)
{
/* Send messages to the first VNC server around */
struct monitor *mon = NULL;
for(int i = 0; i < MONITOR_COUNT && !mon; i++)
mon = app.monitors[i];
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 && mon)
SendKeyEvent(mon->client, code, down);
}
}
else {
hlog("cgvm");
log_("got unknown message: application '%.16s', type '%.16s'\n",
msg->application, msg->type);
}
}
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);
}
static struct monitor *monitor_create(char *server)
{
struct monitor *mon = calloc(1, sizeof *mon);
assert(mon && "out of memory");
/** Configure client **/
rfbClient *client = rfbGetClient(8, 3, 4);
if(!client) {
fprintf(stderr, "rfbGetClient failed\n");
return NULL;
}
rfbClientSetClientData(client, NULL, mon);
client->FinishedFrameBufferUpdate = fb_update;
client->width = CALC_WIDTH;
client->height = CALC_HEIGHT;
client->frameBuffer = malloc(CALC_WIDTH * CALC_HEIGHT * 4);
assert(client->frameBuffer && "out of memory");
/* Standard 32-bit xRGB */
client->format.bitsPerPixel = 32;
client->format.redShift = 16;
client->format.greenShift = 8;
client->format.blueShift = 0;
client->format.redMax = 0xff;
client->format.greenMax = 0xff;
client->format.blueMax = 0xff;
SetFormatAndEncodings(client);
/** Connect to VNC server **/
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;
char *argv[] = { "cgvm_vnc", "-encodings", "raw", server, NULL };
if(!rfbInitClient(client, &argc, argv)) {
fprintf(stderr, "rfbInitClient on server %s failed\n", server);
return NULL;
}
mon->client = client;
/** Create the SDL window if SDL display is requested **/
if(app.display_sdl) {
if(!SDL_WasInit(SDL_INIT_VIDEO))
SDL_Init(SDL_INIT_VIDEO);
mon->window = SDL_CreateWindow("CG Virtual Monitor",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
CALC_WIDTH, CALC_HEIGHT, 0);
mon->surface = SDL_GetWindowSurface(mon->window);
/* Surely this is gonna be the default everywhere, like surely */
assert(mon->surface->format->BytesPerPixel == 4);
assert(mon->surface->format->format == SDL_PIXELFORMAT_RGB888);
}
/** Create calculator framebuffer and stay ready to connect anytime */
mon->fb16_be = malloc(CALC_WIDTH * CALC_HEIGHT * 2);
assert(mon->fb16_be && "out of memory");
return mon;
}
/* Static monitor assignment. If a calculator with the specified serial number
is found, this function determined the only monitor that goes on it. */
static int monitor_assignment(struct fxlink_device *fdev)
{
if(!fdev->calc || !fdev->calc->serial);
return -1;
if(!strcmp(fdev->calc->serial, "IGQcGRe9")) /* Lephe's Graph 90+E */
return 0;
if(!strcmp(fdev->calc->serial, "hULOJWGL")) /* Lephe's fx-CG 50 */
return 1;
return -1;
}
void monitor_update(struct monitor *mon, int mon_id)
{
/* Check if the calculator disconnected */
if(mon->calc) {
bool still_here = false;
for(int i = 0; i < app.devices.count; i++) {
still_here |= app.devices.devices[i].dp == mon->calc_unique_id;
}
if(!still_here) {
hlog("cgvm");
log_("calculator disconnected!\n");
mon->calc = NULL;
mon->calc_unique_id = NULL;
}
}
/* Check for devices ready to connect to */
if(!mon->calc) {
for(int i = 0; i < app.devices.count; i++) {
struct fxlink_device *fdev = &app.devices.devices[i];
char const *id = fxlink_device_id(fdev);
if(fdev->status != FXLINK_FDEV_STATUS_IDLE || !fdev->comm)
continue;
if(fdev->comm->ep_bulk_IN == 0xff) {
hlog("cgvm");
log_("ignoring %s: no fxlink interface\n", id);
continue;
}
int assigned_id = monitor_assignment(fdev);
if(assigned_id >= 0 && assigned_id != mon_id) {
hlog("cgvm");
log_("reserving %s for its statically-assigned monitor %d\n",
assigned_id);
}
if(!fxlink_device_claim_fxlink(fdev))
continue;
hlog("cgvm");
log_("starting virtual monitor #%d on %s (serial: %s)\n",
mon_id, id, fdev->calc->serial);
mon->calc = fdev;
mon->calc_unique_id = fdev->dp;
fxlink_device_start_bulk_IN(fdev);
break;
}
}
/* Handle incoming transfers from the calc */
if(mon->calc) {
struct fxlink_message *msg = fxlink_device_finish_bulk_IN(mon->calc);
if(msg) {
handle_calc_message(msg);
fxlink_message_free(msg, true);
fxlink_device_start_bulk_IN(mon->calc);
}
}
}
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);
/* TODO: Sometimes when the calculator disconnects the wait on the RFB
server loops and can't be killed by SIGINT or SIGTERM? */
signal(SIGINT, exit);
/** Initialize libusb to find calculators **/
if(app.display_calc) {
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();
/* Track the list of connected calculators. */
fxlink_device_list_track(&app.devices, app.libusb_ctx);
}
/** Initialize monitors **/
app.monitors[0] = monitor_create("127.0.0.1:5900");
app.monitors[1] = monitor_create("127.0.0.1:5910");
if(!app.monitors[0] && !app.monitors[1])
return 1;
while(1) {
SDL_Event e;
while(SDL_PollEvent(&e)) {
if(e.type == SDL_QUIT) {
fprintf(stderr, "SDL_QUIT: Exiting...\n");
exit(0);
if(app.display_sdl) {
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;
/** Process messages from all monitors **/
for(int i = 0; i < MONITOR_COUNT; i++) {
struct monitor *mon = app.monitors[i];
if(!mon)
continue;
int rc = WaitForMessage(mon->client, 1000);
if(rc < 0) {
fprintf(stderr, "WaitForMessage() select: %d\n", rc);
continue;
}
if(rc > 0 && !HandleRFBServerMessage(mon->client)) {
fprintf(stderr, "HandleRFBServerMessage() failed\n");
continue;
}
}
else if(i > 0 && !HandleRFBServerMessage(app.client)) {
fprintf(stderr, "HandleRFBServerMessage() failed\n");
continue;
/* Run libusb's event loop */
struct timeval zero_tv = { 0 };
libusb_handle_events_timeout(app.libusb_ctx, &zero_tv);
fxlink_device_list_refresh(&app.devices);
/* Update monitors so they can attach to calculators */
for(int i = 0; i < MONITOR_COUNT; i++) {
struct monitor *mon = app.monitors[i];
if(mon)
monitor_update(mon, i);
}
}