Remote View

Sources: source/internal/remoteview/remoteviewfw.c, server.c, capture.c

Remote View lets any browser on the LAN see and control a live FWH window. The window is captured in real time by GDI+, streamed as JPEG frames over a WebSocket, and drawn on an HTML5 canvas. Mouse clicks, movement, wheel, and keyboard input flow back from the browser to the real window via PostMessage.

Quick Start

Start a server in ON INIT and stop it in VALID. Point any browser on the LAN to http://<ip>:8080:

#include "FiveWin.ch"

function Main()

   local oWnd, oSay, oBtn
   local nClicks := 0

   DEFINE WINDOW oWnd TITLE "FWH Remote View" ;
      FROM 4, 4 TO 22, 60

   @ 1, 2 SAY oSay PROMPT "Open http://localhost:8080" ;
      OF oWnd SIZE 300, 20

   @ 3, 2 BUTTON oBtn PROMPT "Click (local or remote)" ;
      OF oWnd SIZE 160, 30 ;
      ACTION ( nClicks++, oSay:SetText( "Clicks: " + Str( nClicks, 4 ) ) )

   ACTIVATE WINDOW oWnd ;
      ON INIT ( RemoteViewStart( oWnd:hWnd, 8080 ), ;
                oWnd:SetText( "FWH Remote View - http://localhost:8080" ) ) ;
      VALID ( RemoteViewStop(), .T. )

return nil

The browser shows the window as-is — buttons, SAYs, GETs, browses, menus, all work. Clicks on the canvas become real clicks on the real window.

Architecture

flowchart LR subgraph FWH App W[Window] RV[remoteviewfw.c] S[server.c WebSocket] C[capture.c GDI+] end subgraph Browser CV[HTML5 Canvas] JS[JS input send] end W -->|GDI+ capture| C C -->|JPEG frames| S S -->|WebSocket binary| JS JS -->|drawImage| CV CV -->|pointer/key events| JS JS -->|WebSocket 0x10-0x16| S S -->|PostMessage| W RV -->|start/stop| S RV -->|start/stop| C
ComponentFileRole
Harbour binding remoteviewfw.c Exposes RemoteViewStart/Stop/Serve/StopAll/SetOffset to PRG
WebSocket server server.c Single-threaded non-blocking Winsock server, client HTML/JS, binary protocol
Capture engine capture.c GDI+ screen capture, differential (changed-region) JPEG encoding, viewport mode
WebSocket handshake sha1.c, base64.c SHA-1 hashing and Base64 encoding for the WebSocket upgrade handshake

Harbour API Reference

RemoteViewStart( [hWnd], [nPort] ) → lOk

Starts capturing hWnd (default: the active window) and serves it on nPort (default 8080). Returns .T. on success.

Pump strategy: A hidden message-only window owns a WM_TIMER (16 ms) that calls ServerTick(). Because the timer is dispatched by the thread message loop, it keeps firing even while a modal loop (MsgInfo, dialogs, menus) has taken over the loop — exactly what FWH needs. No threads, no -mt required.

RemoteViewStop()

Stops the server, kills the pump timer, frees GDI+, and clears state. Call in VALID or before shutdown.

RemoteViewSetOffset( nX, nY )

Shifts the window image inside the browser viewport by nX pixels right and nY pixels down. Default 0,0. Useful during development so the real window does not cover the web image on the same monitor. Call before RemoteViewStart().

RemoteViewServe( hWnd, nPort [, nOffX, nOffY] )

Blocking. Starts a server with state isolated by TLS (thread-local storage) and pumps ServerTick() in a loop until RemoteViewStopAll() is called. Designed to run inside a Harbour thread created with hb_threadStart(), so each PRG callback (ACTION, bChanged…) executes with the Harbour VM already initialized in that thread. Requires -mt (Harbour MT VM).

RemoteViewStopAll()

Signals all running RemoteViewServe() loops to exit. Call in VALID when using the multi-client MT approach.

Two Modes

Single-Client Mode (WM_TIMER pump)

Use RemoteViewStart() in ON INIT. One server, one client. No -mt needed. The timer pump runs in the GUI thread and survives modal loops. Ideal for most applications.

SampleDescription
samples/RemoteView/testrv.prg Basic window with SAY + BUTTON, single client on port 8080
samples/RemoteView/testrv2.prg Main window + child dialog. The capture composites owned popups, so the dialog appears in the browser too
samples/RemoteView/testrvweb.prg Remote View + TWebView2 (Edge HTML/JS inside the published window)
samples/database/fivedburv.prg FiveDBU integrated with Remote View: ON INIT RemoteViewStart() + right-click popup menu

Multi-Client Mode (Harbour threads + -mt)

Use hb_threadStart() to launch N instances of RemoteViewServe(), each on its own port. TLS state isolation prevents the instances from stepping on each other. Multiple browsers can view and control the same window simultaneously, each on a different port. Stop with RemoteViewStopAll().

#define N_CLIENTS  3
#define BASE_PORT  8080

STATIC FUNCTION StartServers( oWnd )

   local i

   for i := 0 TO N_CLIENTS - 1
      hb_threadStart( {| h, p | RemoteViewServe( h, p, 200, 200 ) }, ;
                      oWnd:hWnd, BASE_PORT + i )
   next

   oWnd:SetText( "FWH Remote View MT - " + LTrim( Str( N_CLIENTS ) ) + ;
                 " servers (8080.." + LTrim( Str( BASE_PORT + N_CLIENTS - 1 ) ) + ")" )

return nil

Sample: samples/RemoteView/testrvmt.prg. Compile with build_new.bat testrvmt mt (mt as 3rd argument selects the Harbour MT VM).

Browser-Client Protocol

The server serves an inline HTML/JS client on GET /. The built-in client opens a WebSocket to /ws and uses a compact binary protocol:

Byte 0MessagePayload
0x01Full framew(2) h(2) originX(2) originY(2) JPEG...
0x02Dirty patchx(2) y(2) w(2) h(2) originX(2) originY(2) JPEG...
0x10Mouse movex(2) y(2)
0x11Mouse downx(2) y(2) button(1)
0x12Mouse upx(2) y(2) button(1)
0x13Key downvk(2) modifiers(1) char(2)
0x14Key upvk(2) modifiers(1)
0x15Mouse wheelx(2) y(2) delta(2)
0x16Viewport sizew(2) h(2) (client → server)

All multi-byte integers are little-endian. Coordinates are in the bitmap space (mapped to screen coordinates by the server before PostMessage).

Smart Capture

The capture engine uses several techniques to minimize bandwidth:

Title Bar Interaction

The browser client supports full title-bar interaction:

Keyboard Support

The browser client captures keyboard events and maps them to Windows virtual-key codes. Standard keys (Enter, Escape, Backspace, Tab, arrows, function keys, alphanumeric) are supported. Modifier keys (Shift, Ctrl, Alt, Meta, CapsLock) are tracked and sent with each key event.

Security Note

Warning: The server binds INADDR_ANY (0.0.0.0) with no authentication. Any device on the LAN can see and control the window. Use only on trusted networks until a local-only or authentication option is added.

Compilation

Remote View is linked into all FWH libraries — no extra libs needed. The WebSocket server uses Winsock (ws2_32.lib) and the capture engine uses GDI+ (gdiplus.lib). Both are linked automatically by build_new.bat for all compiler variants.

The internal RemoteView.exe in source/internal/remoteview/ is a pure-C reference implementation that does not require FiveWin — a minimal Win32 window served by the same server and capture engine.

Compiler Support

The single-client Remote View was verified to capture and stream correctly on every Harbour and xHarbour variant, 32 and 64-bit, across the BCC, MSVC and MinGW C compilers. The testrv / testrvmt window title reports the running build at runtime (for example “Harbour MSVC 64”), which makes it easy to confirm which toolchain produced a given executable.

The multi-client mode (-mt) relies on the Harbour multi-thread VM and its threading C API, so it is available on the Harbour BCC32 / MSVC32 / MSVC64 builds; xHarbour does not provide that API.

xHarbour Commercial (VC98) is the one exception: ordinary FiveWin applications build and run there, but the GDI+ window capture that Remote View depends on (the same path used by SaveAsImage) misbehaves at runtime, so Remote View is not supported on that build.

Integration Pattern

The recommended pattern for adding Remote View to any FWH application:

ACTIVATE WINDOW oWnd ;
   ON INIT ( RemoteViewSetOffset( 200, 200 ), ;          // optional dev offset
             If( RemoteViewStart( oWnd:hWnd, 8080 ), ;
                 oWnd:SetText( "App - http://localhost:8080" ), ;
                 MsgStop( "Could not start Remote View" ) ) ) ;
   VALID ( RemoteViewStop(), .T. )

For apps that need multiple simultaneous clients, use hb_threadStart + RemoteViewServe + RemoteViewStopAll (see Multi-Client Mode above).

Samples

FileDescription
samples/RemoteView/testrv.prg Minimal single-client demo: window + SAY + BUTTON on port 8080
samples/RemoteView/testrv2.prg Main window + child dialog (owned popup compositing)
samples/RemoteView/testrvmt.prg Multi-client: 3 servers on ports 8080–8082 via Harbour threads (-mt)
samples/RemoteView/testrvauth.prg Login before launch: remote user authenticates in a dialog, then the app UI opens
samples/RemoteView/testrvweb.prg Remote View + TWebView2: Edge WebView2 (HTML/JS, SendToFWH) inside a window published with RemoteViewStart
samples/database/fivedburv.prg FiveDBU with integrated Remote View on port 8080

Remote View + WebView2

testrvweb.prg shows how to embed TWebView2 (Microsoft Edge) in a FiveWin window and publish that same window with RemoteViewStart. Locally you get a native toolbar plus the web surface (demo HTML, Navigate, Eval, SendToFWHbOnBind). Remotely, any browser on the LAN opens http://host:8080 and sees/controls the whole window (FWH controls and WebView2 content captured via PW_RENDERFULLCONTENT).

Same-screen layout: put the real desktop window low on the screen (FROM nTop, nLeft TO … PIXEL) so the browser page (the live image) can sit above it. Use a modest RemoteViewSetOffset( nX, nY ) so the window paints near the top of the Remote View bitmap. Requires the WebView2 Runtime (Edge Evergreen or Fixed Version). Compile: build_new.bat testrvweb hm64 (or any single-client variant; no -mt needed).

Authentication Pattern (login before launch)

testrvauth.prg shows how to make a remote user identify before the application is shown. Because RemoteView streams an already-running window and composites its owned popups, the login is just a normal FiveWin dialog owned by the served window — the remote user sees it and fills it in. On valid credentials the app UI opens (also owned by the served window, so it appears instantly); on failure the session is closed.

Gotcha — do not show modal UI inside ON INIT. If you launch the login dialog synchronously in ON INIT, the host window has not presented its first frame yet, so PrintWindow (the capture path) blits it black before and during login. Defer the login until after the window paints — a one-shot TTimer works well:

ACTIVATE WINDOW oWnd ON INIT ( RemoteViewStart( oWnd:hWnd, 8080 ), DeferLogin( oWnd ) )

STATIC FUNCTION DeferLogin( oWnd )
   LOCAL oTmr
   DEFINE TIMER oTmr INTERVAL 250 OF oWnd ;
      ACTION ( oTmr:DeActivate(), Login( oWnd ) )   // 1-shot: deactivate, then show login
   ACTIVATE TIMER oTmr
RETURN NIL

For real deployments authenticate over the TLS multi-client path (RemoteViewServe + -mt) and store credentials hashed: the single-client path is plain HTTP/WS, so credentials would travel in clear.