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

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
ComponenteFicheiroFunçã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.

ExemploDescriçã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 0MensagemCarga útil
0x01Quadro completow(2) h(2) originX(2) originY(2) JPEG...
0x02Patch sujox(2) y(2) w(2) h(2) originX(2) originY(2) JPEG...
0x10Movimento do ratox(2) y(2)
0x11Botão do rato premidox(2) y(2) button(1)
0x12Botão do rato libertadox(2) y(2) button(1)
0x13Tecla premidavk(2) modifiers(1) char(2)
0x14Tecla libertadavk(2) modifiers(1)
0x15Roda do ratox(2) y(2) delta(2)
0x16Tamanho do viewportw(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:

Interação com a Barra de Título

O cliente do navegador suporta interação completa com a barra de título:

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.

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

FicheiroDescriçã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, SendToFWHbOnBind). 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) bli­ta-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.