Remote View

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

Remote View permite que cualquier navegador de la LAN vea y controle en directo una ventana FWH activa. La ventana se captura en tiempo real con GDI+, se transmite como fotogramas JPEG a través de un WebSocket y se dibuja en un canvas HTML5. Los clics del ratón, el movimiento, la rueda y la entrada de teclado regresan desde el navegador a la ventana real mediante PostMessage.

Inicio Rápido

Inicie un servidor en ON INIT y deténgalo en VALID. Apunte cualquier navegador de la LAN a 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

El navegador muestra la ventana tal cual — botones, SAYs, GETs, browses, menús, todo funciona. Los clics en el canvas se convierten en clics reales sobre la ventana real.

Arquitectura

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
ComponenteArchivoFunción
Enlace con Harbour remoteviewfw.c Expone RemoteViewStart/Stop/Serve/StopAll/SetOffset al PRG
Servidor WebSocket server.c Servidor Winsock de un solo hilo y sin bloqueo, HTML/JS del cliente, protocolo binario
Motor de captura capture.c Captura de pantalla con GDI+, codificación JPEG diferencial (región cambiada), modo viewport
Handshake WebSocket sha1.c, base64.c Hash SHA-1 y codificación Base64 para el handshake de actualización WebSocket

Referencia de la API de Harbour

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

Inicia la captura de hWnd (por defecto: la ventana activa) y la sirve en nPort (por defecto 8080). Devuelve .T. si tiene éxito.

Estrategia de bombeo: Una ventana oculta de solo mensajes posee un WM_TIMER (16 ms) que llama a ServerTick(). Dado que el temporizador lo despacha el bucle de mensajes del hilo, sigue disparándose incluso mientras un bucle modal (MsgInfo, diálogos, menús) ha tomado el control del bucle — justo lo que FWH necesita. Sin hilos, sin -mt requerido.

RemoteViewStop()

Detiene el servidor, elimina el temporizador de bombeo, libera GDI+ y limpia el estado. Llámelo en VALID o antes del cierre.

RemoteViewSetOffset( nX, nY )

Desplaza la imagen de la ventana dentro del viewport del navegador nX píxeles a la derecha y nY píxeles hacia abajo. Por defecto 0,0. Útil durante el desarrollo para que la ventana real no cubra la imagen web en el mismo monitor. Llámelo antes de RemoteViewStart().

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

Bloqueante. Inicia un servidor con el estado aislado mediante TLS (almacenamiento local de hilo) y bombea ServerTick() en un bucle hasta que se llama a RemoteViewStopAll(). Diseñado para ejecutarse dentro de un hilo de Harbour creado con hb_threadStart(), de modo que cada callback PRG (ACTION, bChanged…) se ejecute con la VM de Harbour ya inicializada en ese hilo. Requiere -mt (VM MT de Harbour).

RemoteViewStopAll()

Indica a todos los bucles RemoteViewServe() en ejecución que finalicen. Llámelo en VALID cuando use el enfoque multicliente MT.

Dos Modos

Modo Cliente Único (bombeo WM_TIMER)

Use RemoteViewStart() en ON INIT. Un servidor, un cliente. No se necesita -mt. El bombeo del temporizador se ejecuta en el hilo de la GUI y sobrevive a los bucles modales. Ideal para la mayoría de las aplicaciones.

EjemploDescripción
samples/RemoteView/testrv.prg Ventana básica con SAY + BUTTON, un cliente en el puerto 8080
samples/RemoteView/testrv2.prg Ventana principal + diálogo hijo. La captura compone los popups propios, por lo que el diálogo también aparece en el navegador
samples/RemoteView/testrvweb.prg Remote View + TWebView2 (Edge HTML/JS dentro de la ventana publicada)
samples/database/fivedburv.prg FiveDBU integrado con Remote View: ON INIT RemoteViewStart() + menú emergente con clic derecho

Modo Multicliente (hilos de Harbour + -mt)

Use hb_threadStart() para lanzar N instancias de RemoteViewServe(), cada una en su propio puerto. El aislamiento de estado por TLS evita que las instancias se pisen entre sí. Varios navegadores pueden ver y controlar la misma ventana simultáneamente, cada uno en un puerto distinto. Deténgalos con 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

Ejemplo: samples/RemoteView/testrvmt.prg. Compílelo con build_new.bat testrvmt mt (mt como 3er argumento selecciona la VM MT de Harbour).

Protocolo Cliente-Navegador

El servidor sirve un cliente HTML/JS en línea en GET /. El cliente integrado abre un WebSocket hacia /ws y usa un protocolo binario compacto:

Byte 0MensajeCarga útil
0x01Fotograma completow(2) h(2) originX(2) originY(2) JPEG...
0x02Parche suciox(2) y(2) w(2) h(2) originX(2) originY(2) JPEG...
0x10Movimiento de ratónx(2) y(2)
0x11Botón de ratón pulsadox(2) y(2) button(1)
0x12Botón de ratón liberadox(2) y(2) button(1)
0x13Tecla pulsadavk(2) modifiers(1) char(2)
0x14Tecla liberadavk(2) modifiers(1)
0x15Rueda del ratónx(2) y(2) delta(2)
0x16Tamaño del viewportw(2) h(2) (cliente → servidor)

Todos los enteros multibyte son little-endian. Las coordenadas están en el espacio del bitmap (el servidor las asigna a coordenadas de pantalla antes de PostMessage).

Captura Inteligente

El motor de captura usa varias técnicas para minimizar el ancho de banda:

Interacción con la Barra de Título

El cliente del navegador admite la interacción completa con la barra de título:

Soporte de Teclado

El cliente del navegador captura los eventos de teclado y los asigna a los códigos de tecla virtual de Windows. Se admiten las teclas estándar (Enter, Escape, Retroceso, Tab, flechas, teclas de función, alfanuméricas). Las teclas modificadoras (Shift, Ctrl, Alt, Meta, BloqMayús) se rastrean y se envían con cada evento de tecla.

Nota de Seguridad

Advertencia: El servidor se enlaza a INADDR_ANY (0.0.0.0) sin autenticación. Cualquier dispositivo de la LAN puede ver y controlar la ventana. Úselo solo en redes de confianza hasta que se añada una opción de solo local o de autenticación.

Compilación

Remote View está enlazado en todas las bibliotecas FWH — no se necesitan libs adicionales. El servidor WebSocket usa Winsock (ws2_32.lib) y el motor de captura usa GDI+ (gdiplus.lib). build_new.bat enlaza ambos automáticamente para todas las variantes de compilador.

El RemoteView.exe interno en source/internal/remoteview/ es una implementación de referencia en C puro que no requiere FiveWin — una ventana Win32 mínima servida por el mismo servidor y motor de captura.

Compatibilidad con Compiladores

Se verificó que el Remote View de cliente único captura y transmite correctamente en todas las variantes de Harbour y xHarbour, de 32 y 64 bits, en los compiladores C BCC, MSVC y MinGW. El título de la ventana testrv / testrvmt informa de la compilación en ejecución en tiempo de ejecución (por ejemplo “Harbour MSVC 64”), lo que facilita confirmar qué cadena de herramientas produjo un ejecutable dado.

El modo multicliente (-mt) se basa en la VM multihilo de Harbour y su API C de hilos, por lo que está disponible en las compilaciones Harbour BCC32 / MSVC32 / MSVC64; xHarbour no proporciona esa API.

xHarbour Commercial (VC98) es la única excepción: las aplicaciones FiveWin ordinarias se compilan y ejecutan allí, pero la captura de ventanas con GDI+ de la que depende Remote View (la misma ruta usada por SaveAsImage) funciona mal en tiempo de ejecución, por lo que Remote View no es compatible con esa compilación.

Patrón de Integración

El patrón recomendado para añadir Remote View a cualquier aplicación 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 aplicaciones que necesitan varios clientes simultáneos, use hb_threadStart + RemoteViewServe + RemoteViewStopAll (vea Modo Multicliente más arriba).

Ejemplos

ArchivoDescripción
samples/RemoteView/testrv.prg Demo mínima de cliente único: ventana + SAY + BUTTON en el puerto 8080
samples/RemoteView/testrv2.prg Ventana principal + diálogo hijo (composición de popups propios)
samples/RemoteView/testrvmt.prg Multicliente: 3 servidores en los puertos 8080–8082 mediante hilos de Harbour (-mt)
samples/RemoteView/testrvauth.prg Login antes de lanzar: el usuario remoto se identifica en un diálogo y luego se abre la app
samples/RemoteView/testrvweb.prg Remote View + TWebView2: Edge WebView2 (HTML/JS, SendToFWH) dentro de una ventana publicada con RemoteViewStart
samples/database/fivedburv.prg FiveDBU con Remote View integrado en el puerto 8080

Remote View + WebView2

testrvweb.prg muestra cómo embeber TWebView2 (Microsoft Edge) en una ventana FiveWin y publicarla con RemoteViewStart. En local: barra de botones nativos más la superficie web (HTML demo, Navigate, Eval, SendToFWHbOnBind). En remoto: cualquier navegador de la LAN abre http://host:8080 y ve/controla toda la ventana (controles FWH y contenido WebView2 capturado con PW_RENDERFULLCONTENT).

Disposición en la misma pantalla: sitúa la ventana desktop abajo (FROM nTop, nLeft TO … PIXEL) para que la página del navegador (la imagen en vivo) quede arriba. Usa un RemoteViewSetOffset( nX, nY ) modesto para dibujar la ventana cerca del borde superior del bitmap. Requiere Runtime WebView2 (Edge Evergreen o Fixed Version). Compilar: build_new.bat testrvweb hm64 (cualquier variante mono-cliente; no hace falta -mt).

Patrón de autenticación (login antes de lanzar)

testrvauth.prg muestra cómo hacer que un usuario remoto se identifique antes de mostrar la aplicación. Como RemoteView transmite una ventana ya en marcha y composita sus popups owned, el login es un diálogo FiveWin normal owned por la ventana servida — el remoto lo ve y lo rellena. Con credenciales válidas se abre la UI de la app (también owned por la ventana servida, así aparece al instante); si fallan, se cierra la sesión.

Cuidado — no muestres UI modal dentro de ON INIT. Si lanzas el diálogo de login de forma síncrona en ON INIT, la ventana anfitriona aún no ha presentado su primer frame, así que PrintWindow (la ruta de captura) la bli­tea negra antes y durante el login. Difiere el login hasta que la ventana pinte — un TTimer de un disparo va bien:

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: desactiva y muestra el login
   ACTIVATE TIMER oTmr
RETURN NIL

En producción autentica sobre la ruta multicliente con TLS (RemoteViewServe + -mt) y guarda las credenciales hasheadas: la ruta single-client es HTTP/WS plano y viajarían en claro.