Compare commits
10 Commits
1493645f98
...
a3864fad21
Author | SHA1 | Date |
---|---|---|
Lephenixnoir | a3864fad21 | |
Lephenixnoir | 94cfbee2e6 | |
Lephenixnoir | 9319f162bc | |
Lephenixnoir | 74a7f05e9d | |
Lephenixnoir | b6c782666c | |
Lephenixnoir | fb9174197f | |
Lephenixnoir | 9e76db9e7e | |
Lephenixnoir | c0b1ced527 | |
Lephenixnoir | 1be08a29df | |
Lephenixnoir | b39ccf9758 |
26
README.md
26
README.md
|
@ -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
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue