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
| Component | File | Role |
|---|---|---|
| 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.
| Sample | Description |
|---|---|
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 0 | Message | Payload |
|---|---|---|
0x01 | Full frame | w(2) h(2) originX(2) originY(2) JPEG... |
0x02 | Dirty patch | x(2) y(2) w(2) h(2) originX(2) originY(2) JPEG... |
0x10 | Mouse move | x(2) y(2) |
0x11 | Mouse down | x(2) y(2) button(1) |
0x12 | Mouse up | x(2) y(2) button(1) |
0x13 | Key down | vk(2) modifiers(1) char(2) |
0x14 | Key up | vk(2) modifiers(1) |
0x15 | Mouse wheel | x(2) y(2) delta(2) |
0x16 | Viewport size | w(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:
- Differential capture: Only the changed region of the
window is JPEG-encoded and sent as a
0x02patch. The browser draws it at the correct offset, preserving the rest of the canvas. - FNV-1a hash: Every frame is hashed. Identical consecutive frames are skipped entirely — no JPEG encode, no network send.
- Viewport mode: When the browser sends its viewport size
(
0x16), the capture engine scales the virtual desktop to match the browser canvas. The window is centered within this virtual space. Maximizing the window fills the browser viewport, not the real monitor. - Minimize: When the window is minimized, an iconic representation is drawn and clickable in the browser.
- Owned popups: Dialogs and MessageBoxes owned by the captured window are composited on top, so they appear in the browser.
Title Bar Interaction
The browser client supports full title-bar interaction:
- Drag to move: Click and drag the title bar area to move the window
- Resize edges: 8-pixel borders on all sides and corners for resizing
- Double-click title bar: Toggles maximize/restore
- Min/Max/Close buttons: Clickable with visual hover feedback
- DWM-aware: Adjusts for modern Windows 10/11 DWM extended frame bounds so button hit-testing works correctly with rounded corners and shadows
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.
- Single client:
build_new.bat testrv(any variant) - Multi-client:
build_new.bat testrvmt mt(Harbour MT VM required)
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
| File | Description |
|---|---|
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,
SendToFWH → bOnBind). 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.