Remote View
Fontes: source/internal/remoteview/remoteviewfw.c, server.c, capture.c
O Remote View permite que qualquer navegador na rede local (LAN) veja e controle
uma janela FWH ao vivo. A janela é capturada em tempo real pelo GDI+,
transmitida como quadros JPEG sobre um WebSocket e desenhada num canvas HTML5.
Cliques de rato, movimento, roda e entrada de teclado regressam do navegador
para a janela real através de PostMessage.
Início Rápido
Inicie um servidor em ON INIT e pare-o em VALID.
Aponte qualquer navegador da LAN para 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
O navegador mostra a janela tal como está — botões, SAYs, GETs, browses, menus, tudo funciona. Cliques no canvas tornam-se cliques reais na janela real.
Arquitetura
| Componente | Ficheiro | Função |
|---|---|---|
| Binding Harbour | remoteviewfw.c |
Expõe RemoteViewStart/Stop/Serve/StopAll/SetOffset ao PRG |
| Servidor WebSocket | server.c |
Servidor Winsock não-bloqueante de thread única, HTML/JS do cliente, protocolo binário |
| Motor de captura | capture.c |
Captura de ecrã GDI+, codificação JPEG diferencial (região alterada), modo viewport |
| Handshake WebSocket | sha1.c, base64.c |
Hashing SHA-1 e codificação Base64 para o handshake de upgrade do WebSocket |
Referência da API Harbour
RemoteViewStart( [hWnd], [nPort] ) → lOk
Inicia a captura de hWnd (predefinição: a janela ativa) e
disponibiliza-a em nPort (predefinição 8080). Devolve
.T. em caso de sucesso.
Estratégia de bombeamento (pump): Uma janela oculta
exclusiva de mensagens detém um WM_TIMER (16 ms) que chama
ServerTick(). Como o temporizador é despachado pelo ciclo de
mensagens da thread, continua a disparar mesmo enquanto um ciclo modal (MsgInfo,
diálogos, menus) tomou conta do ciclo — exatamente o que o FWH precisa.
Sem threads, sem -mt necessário.
RemoteViewStop()
Para o servidor, elimina o temporizador de pump, liberta o GDI+ e limpa o estado.
Chame em VALID ou antes de encerrar.
RemoteViewSetOffset( nX, nY )
Desloca a imagem da janela dentro do viewport do navegador nX pixels
para a direita e nY pixels para baixo. Predefinição 0,0.
Útil durante o desenvolvimento para que a janela real não cubra a
imagem web no mesmo monitor. Chame antes de RemoteViewStart().
RemoteViewServe( hWnd, nPort [, nOffX, nOffY] )
Bloqueante. Inicia um servidor com o estado isolado por TLS
(armazenamento local de thread) e bombeia ServerTick() num ciclo até
que RemoteViewStopAll() seja chamado. Concebido para correr dentro de
uma thread Harbour criada com hb_threadStart(), de modo que cada
callback PRG (ACTION, bChanged…) executa com a VM Harbour já
inicializada nessa thread. Requer -mt (VM MT do Harbour).
RemoteViewStopAll()
Sinaliza todos os ciclos RemoteViewServe() em execução para
terminarem. Chame em VALID ao usar a abordagem MT multi-cliente.
Dois Modos
Modo Cliente Único (pump WM_TIMER)
Use RemoteViewStart() em ON INIT. Um servidor, um cliente.
Sem -mt necessário. O pump por temporizador corre na thread da GUI
e sobrevive a ciclos modais. Ideal para a maioria das aplicações.
| Exemplo | Descrição |
|---|---|
samples/RemoteView/testrv.prg |
Janela básica com SAY + BUTTON, cliente único na porta 8080 |
samples/RemoteView/testrv2.prg |
Janela principal + diálogo filho. A captura compõe os popups pertencentes, pelo que o diálogo aparece também no navegador |
samples/RemoteView/testrvweb.prg |
Remote View + TWebView2 (Edge HTML/JS dentro da janela publicada) |
samples/database/fivedburv.prg |
FiveDBU integrado com Remote View: ON INIT RemoteViewStart() + menu popup com botão direito |
Modo Multi-Cliente (threads Harbour + -mt)
Use hb_threadStart() para lançar N instâncias de
RemoteViewServe(), cada uma na sua própria porta. O isolamento
de estado por TLS impede que as instâncias interfiram umas com as outras.
Vários navegadores podem ver e controlar a mesma janela em simultâneo,
cada um numa porta diferente. Pare com 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
Exemplo: samples/RemoteView/testrvmt.prg.
Compile com build_new.bat testrvmt mt
(mt como 3º argumento seleciona a VM MT do Harbour).
Protocolo Navegador-Cliente
O servidor disponibiliza um cliente HTML/JS embutido em GET /. O cliente
integrado abre um WebSocket para /ws e usa um protocolo binário
compacto:
| Byte 0 | Mensagem | Carga útil |
|---|---|---|
0x01 | Quadro completo | w(2) h(2) originX(2) originY(2) JPEG... |
0x02 | Patch sujo | x(2) y(2) w(2) h(2) originX(2) originY(2) JPEG... |
0x10 | Movimento do rato | x(2) y(2) |
0x11 | Botão do rato premido | x(2) y(2) button(1) |
0x12 | Botão do rato libertado | x(2) y(2) button(1) |
0x13 | Tecla premida | vk(2) modifiers(1) char(2) |
0x14 | Tecla libertada | vk(2) modifiers(1) |
0x15 | Roda do rato | x(2) y(2) delta(2) |
0x16 | Tamanho do viewport | w(2) h(2) (cliente → servidor) |
Todos os inteiros multi-byte são little-endian. As coordenadas estão no espaço do bitmap (mapeadas para coordenadas de ecrã pelo servidor antes de PostMessage).
Captura Inteligente
O motor de captura usa várias técnicas para minimizar a largura de banda:
- Captura diferencial: Apenas a região alterada da
janela é codificada em JPEG e enviada como um patch
0x02. O navegador desenha-a no offset correto, preservando o resto do canvas. - Hash FNV-1a: Cada quadro é sujeito a hash. Quadros consecutivos idênticos são totalmente ignorados — sem codificação JPEG, sem envio pela rede.
- Modo viewport: Quando o navegador envia o tamanho do seu
viewport (
0x16), o motor de captura escala o ambiente de trabalho virtual para corresponder ao canvas do navegador. A janela é centrada dentro deste espaço virtual. Maximizar a janela preenche o viewport do navegador, não o monitor real. - Minimizar: Quando a janela é minimizada, é desenhada uma representação icónica, clicável no navegador.
- Popups pertencentes: Diálogos e MessageBoxes pertencentes à janela capturada são compostos por cima, pelo que aparecem no navegador.
Interação com a Barra de Título
O cliente do navegador suporta interação completa com a barra de título:
- Arrastar para mover: Clique e arraste a área da barra de título para mover a janela
- Bordas de redimensionamento: Bordas de 8 pixels em todos os lados e cantos para redimensionar
- Duplo clique na barra de título: Alterna entre maximizar/restaurar
- Botões Min/Max/Fechar: Clicáveis com feedback visual de hover
- Consciente do DWM: Ajusta-se aos limites de moldura estendida do DWM do Windows 10/11 moderno, para que a detetação de cliques nos botões funcione corretamente com cantos arredondados e sombras
Suporte de Teclado
O cliente do navegador captura eventos de teclado e mapeia-os para códigos de teclas virtuais do Windows. Teclas padrão (Enter, Escape, Backspace, Tab, setas, teclas de função, alfanuméricas) são suportadas. Teclas modificadoras (Shift, Ctrl, Alt, Meta, CapsLock) são rastreadas e enviadas com cada evento de tecla.
Nota de Segurança
Aviso: O servidor liga-se a INADDR_ANY (0.0.0.0)
sem autenticação. Qualquer dispositivo na LAN pode ver e controlar a
janela. Use apenas em redes de confiança até que seja
adicionada uma opção apenas-local ou de autenticação.
Compilação
O Remote View está integrado em todas as bibliotecas FWH — não
são necessárias bibliotecas extra. O servidor WebSocket usa Winsock
(ws2_32.lib) e o motor de captura usa GDI+ (gdiplus.lib).
Ambos são ligados automaticamente por build_new.bat para todas
as variantes de compilador.
O RemoteView.exe interno em
source/internal/remoteview/ é uma implementação de
referência em C puro que não requer FiveWin — uma janela Win32
mínima servida pelo mesmo servidor e motor de captura.
- Cliente único:
build_new.bat testrv(qualquer variante) - Multi-cliente:
build_new.bat testrvmt mt(VM MT do Harbour necessária)
Suporte de Compiladores
O Remote View de cliente único foi verificado para capturar e transmitir
corretamente em todas as variantes Harbour e xHarbour, 32 e 64 bits,
nos compiladores C BCC, MSVC e MinGW. O título da janela
testrv / testrvmt reporta o build em
execução em tempo de execução (por exemplo
“Harbour MSVC 64”), o que facilita confirmar qual a toolchain
que produziu um dado executável.
O modo multi-cliente (-mt) depende da VM multi-thread do Harbour e da
sua API C de threading, pelo que está disponível nos builds Harbour
BCC32 / MSVC32 / MSVC64; o xHarbour não fornece essa API.
xHarbour Commercial (VC98) é a única exceção:
aplicações FiveWin comuns compilam e correm aí, mas a captura de
janela GDI+ de que o Remote View depende (o mesmo caminho usado por
SaveAsImage) comporta-se mal em tempo de execução, pelo que
o Remote View não é suportado nesse build.
Padrão de Integração
O padrão recomendado para adicionar o Remote View a qualquer aplicação FWH:
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. )
Para aplicações que precisam de vários clientes em simultâneo,
use hb_threadStart + RemoteViewServe +
RemoteViewStopAll (ver Modo Multi-Cliente acima).
Exemplos
| Ficheiro | Descrição |
|---|---|
samples/RemoteView/testrv.prg |
Demo mínima de cliente único: janela + SAY + BUTTON na porta 8080 |
samples/RemoteView/testrv2.prg |
Janela principal + diálogo filho (composição de popups pertencentes) |
samples/RemoteView/testrvmt.prg |
Multi-cliente: 3 servidores nas portas 8080–8082 via threads Harbour (-mt) |
samples/RemoteView/testrvauth.prg |
Login antes de lançar: o utilizador remoto identifica-se num diálogo e depois a app abre |
samples/RemoteView/testrvweb.prg |
Remote View + TWebView2: Edge WebView2 (HTML/JS, SendToFWH) dentro de uma janela publicada com RemoteViewStart |
samples/database/fivedburv.prg |
FiveDBU com Remote View integrado na porta 8080 |
Remote View + WebView2
testrvweb.prg mostra como embutir
TWebView2 (Microsoft Edge) numa janela FiveWin e publicá-la
com RemoteViewStart. Em local: barra de botões nativos mais
a superfície web (HTML demo, Navigate, Eval,
SendToFWH → bOnBind). Em remoto: qualquer
browser na LAN abre http://host:8080 e vê/controla toda a
janela (controlos FWH e conteúdo WebView2 capturado com
PW_RENDERFULLCONTENT).
Disposição no mesmo ecrã: coloque a
janela desktop em baixo (FROM nTop, nLeft TO … PIXEL) para
a página do browser (a imagem em direto) ficar em cima. Use um
RemoteViewSetOffset( nX, nY ) modesto para desenhar a janela
perto do topo do bitmap. Requer Runtime WebView2 (Edge Evergreen ou Fixed
Version). Compilar: build_new.bat testrvweb hm64 (qualquer
variante mono-cliente; não é preciso -mt).
Padrão de autenticação (login antes de lançar)
testrvauth.prg mostra como fazer um utilizador remoto identificar-se
antes de mostrar a aplicação. Como o RemoteView transmite uma janela já em execução
e compõe os seus popups owned, o login é um diálogo FiveWin normal owned
pela janela servida — o remoto vê-o e preenche-o. Com credenciais válidas a UI
da app abre (também owned pela janela servida, logo aparece de imediato); se falham,
a sessão é fechada.
Atenção — não mostre UI modal dentro de ON INIT.
Se lançar o diálogo de login de forma síncrona em ON INIT, a janela
anfitriã ainda não apresentou o seu primeiro frame, por isso o PrintWindow
(o caminho de captura) blita-a a preto antes e durante o login. Adie o login
até a janela pintar — um TTimer de um disparo funciona bem:
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 disparo: desativa e mostra o login
ACTIVATE TIMER oTmr
RETURN NIL
Em produção autentique sobre o caminho multi-cliente com TLS
(RemoteViewServe + -mt) e guarde as credenciais com hash:
o caminho single-client é HTTP/WS simples e viajariam em claro.