Implementazione di un server Framebuffer remoto in Java
Pubblicato: 2022-03-11In informatica, Virtual Network Computing (VNC) è un sistema grafico di condivisione del desktop che utilizza il protocollo Remote Framebuffer (RFB) per controllare in remoto un altro computer. Trasmette gli eventi della tastiera e del mouse da un computer all'altro e trasmette gli aggiornamenti grafici dello schermo nell'altra direzione su una rete.
RFB è un semplice protocollo per l'accesso remoto alle interfacce utente grafiche. Poiché funziona a livello di frame buffer, è applicabile a tutti i sistemi e le applicazioni di finestre inclusi Microsoft Windows, Mac OS X e X Window System.
In questo articolo mostrerò come implementare il protocollo lato server RFB e dimostrerò con una piccola applicazione Java Swing come trasmettere la finestra principale tramite connessione TCP ai visualizzatori VNC. L'idea è quella di dimostrare le caratteristiche di base del protocollo e la possibile implementazione in Java.
Il lettore dovrebbe avere una conoscenza di base del linguaggio di programmazione Java e dovrebbe avere familiarità con i concetti di base della rete TCP/IP, del modello client-server, ecc. Idealmente, il lettore è uno sviluppatore Java e ha una certa esperienza con implementazioni VNC ben note come RealVNC , UltraVNC, TightVNC, ecc.
Specifica del protocollo Framebuffer remoto
La specifica del protocollo RFB è abbastanza ben definita. Secondo Wikipedia, il protocollo RFB ha diverse versioni. Per questo articolo, ci concentreremo sui messaggi comuni che dovrebbero essere compresi correttamente dalla maggior parte delle implementazioni VNC indipendentemente dalla versione del protocollo.
Dopo che un visualizzatore VNC (client) stabilisce una connessione TCP a un server VNC (servizio RFB), la prima fase prevede lo scambio della versione del protocollo:
RFB Service ----------- "RFB 003.003\n" -------> VNC viewer RFB Service <---------- "RFB 003.008\n" -------- VNC viewer
È un semplice flusso di byte che può essere decodificato in caratteri ASCII, come “RFB 003.008\n”.
Una volta fatto, il passaggio successivo è l'autenticazione. Il server VNC invia un array di byte per indicare il tipo di autenticazione che supporta. Per esempio:
RFB Service ----------- 0x01 0x02 -----------> VNC viewer RFB Service <----------- 0x02 ----------- VNC viewer
Qui il server VNC ha inviato solo 1 possibile tipo di autenticazione (0x02). Il primo byte 0x01 indica il numero di tipi di autenticazione disponibili. Il visualizzatore VNC deve rispondere con il valore 0x02, poiché è l'unico tipo possibile supportato dal server in questo esempio.
Successivamente, il server invierà una richiesta di autenticazione (a seconda dell'algoritmo, ce ne sono diversi) e il client deve rispondere con un messaggio di risposta alla richiesta appropriato e attendere che il server confermi la risposta. Una volta che il client è stato autenticato, può continuare con il processo di creazione della sessione.
Il modo più semplice qui è scegliere l'assenza di autenticazione. Il protocollo RFB non è comunque sicuro, indipendentemente dal meccanismo di autenticazione. Se la sicurezza è importante, il modo corretto sarebbe quello di eseguire il tunneling delle sessioni RFB tramite connessioni VPN o SSH.
A questo punto, il visualizzatore VNC invia un messaggio desktop condiviso che indica se il client condividerà e consentirà ad altri visualizzatori VNC di connettersi allo stesso desktop. Spetta all'implementazione del servizio RFB considerare quel messaggio ed eventualmente impedire a più visualizzatori VNC di condividere lo stesso schermo. Questo messaggio è lungo solo 1 byte e un valore valido è 0x00 o 0x01.
Infine il server RFB invia un messaggio di inizializzazione del server, che contiene la dimensione dello schermo, bit per pixel, profondità, flag big endian e flag true color, valori massimi per i colori rosso, verde e blu, posizioni dei bit in pixel per i colori rosso, verde e blu e stringa/titolo desktop. I primi due byte rappresentano la larghezza dello schermo in pixel, i due byte successivi sono l'altezza dello schermo. Dopo i byte di altezza dello schermo, nel messaggio dovrebbero essere presenti bit per pixel byte. Il valore è in genere 8, 16 o 32. Sulla maggior parte dei sistemi moderni con gamma di colori completa, bit per pixel byte ha valore 32 (0x20). Dice al client che può richiedere il colore completo per ogni pixel dal server. Il byte big endian è diverso da zero solo se i pixel sono nell'ordine big endian. Se true color byte è diverso da zero (true), i successivi sei byte specificano come estrarre le intensità di colore rosso, verde e blu dal valore del pixel. I prossimi sei byte sono i valori massimi consentiti per la componente rossa, verde e blu del pixel. Questo è importante nella modalità colore a 8 bit, dove sono disponibili solo pochi bit per ogni componente colore. Gli spostamenti di rosso, verde e blu determinano le posizioni dei bit per ciascun colore. Gli ultimi tre byte sono riempimento e dovrebbero essere ignorati dal client. Dopo il formato pixel, c'è un byte che definisce la lunghezza di una stringa per il titolo del desktop. Il titolo del desktop è una stringa codificata ASCII in un array di byte di lunghezza arbitraria.
Dopo il messaggio di inizializzazione del server, il servizio RFB dovrebbe leggere i messaggi client dal socket e decodificarli. Esistono 6 tipi di messaggi:
- Imposta formato pixel
- Imposta codifiche
- FramebufferUpdateRequest
- Evento chiave
- PointerEvent
- ClienteCutText
La documentazione del protocollo è piuttosto esatta e spiega ogni messaggio. Per ogni messaggio viene spiegato ogni byte. Ad esempio, messaggio di inizializzazione del server:
Numero di byte | Tipo | Descrizione |
---|---|---|
2 | U16 | larghezza del framebuffer |
2 | U16 | framebuffer-altezza |
16 | FORMATO_PIXEL | formato pixel del server |
4 | U32 | lunghezza del nome |
lunghezza del nome | Matrice U8 | nome-stringa |
Qui, PIXEL_FORMAT è:
Numero di byte | Tipo | Descrizione |
---|---|---|
1 | U8 | bit per pixel |
1 | U8 | profondità |
1 | U8 | big-endian-bandiera |
1 | U8 | true-color-bandiera |
2 | U16 | rosso-max |
2 | U16 | verde-max |
2 | U16 | blu-max |
1 | U8 | spostamento verso il rosso |
1 | U8 | cambio verde |
1 | U8 | spostamento verso il blu |
3 | imbottitura |
U16 significa intero a 16 bit senza segno (due byte), U32 è intero a 32 bit senza segno, l'array U8 è una matrice di byte, ecc.
Implementazione del protocollo in Java
Una tipica applicazione server Java consiste in un thread in ascolto per le connessioni client e diversi thread che gestiscono le connessioni client.
/* * Use TCP port 5902 (display :2) as an example to listen. */ int port = 5902; ServerSocket serverSocket; serverSocket = new ServerSocket(port); /* * Limit sessions to 100. This is lazy way, if * somebody really open 100 sessions, server socket * will stop listening and no new VNC viewers will be * able to connect. */ while (rfbClientList.size() < 100) { /* * Wait and accept new client. */ Socket client = serverSocket.accept(); /* * Create new object for each client. */ RFBService rfbService = new RFBService(client); /* * Add it to list. */ rfbClientList.add(rfbService); /* * Handle new client session in separate thread. */ (new Thread(rfbService, "RFBService" + rfbClientList.size())).start(); }
Qui è stata scelta la porta TCP 5902 (visualizzazione :2) e il ciclo while attende la connessione di un client. Il metodo ServerSocket.accept() sta bloccando e fa attendere il thread per una nuova connessione client. Una volta che il client si è connesso, viene creato un nuovo thread RFBService che gestisce i messaggi del protocollo RFB ricevuti dal client.
La classe RFBService implementa l'interfaccia Runnable. È pieno di metodi per leggere i byte dal socket. Il metodo run() è importante, che viene eseguito immediatamente quando il thread viene avviato alla fine del ciclo:
@Override public void run() { try { /* * RFB server has to send protocol version string first. * And wait for VNC viewer to replay with * protocol version string. */ sendProtocolVersion(); String protocolVer = readProtocolVersion(); if (!protocolVer.startsWith("RFB")) { throw new IOException(); }
Qui il metodo sendProtocolVersion() invia la stringa RFB al client (visualizzatore VNC) e quindi legge la stringa della versione del protocollo dal client. Il cliente dovrebbe rispondere con qualcosa come "RFB 003.008\n". Il metodo readProtocolVersion() è ovviamente bloccante, come qualsiasi metodo il cui nome inizia con la parola read.
private String readProtocolVersion() throws IOException { byte[] buffer = readU8Array(12); return new String(buffer); }
Il metodo readProtocolVersion() è semplice: legge 12 byte dal socket e restituisce un valore stringa. La funzione readU8Array(int) legge il numero specificato di byte, in questo caso 12 byte. Se non ci sono abbastanza byte da leggere sul socket, attende:
private byte[] readU8Array(int len) throws IOException { byte[] buffer = new byte[len]; int offset = 0, left = buffer.length; while (offset < buffer.length) { int numOfBytesRead = 0; numOfBytesRead = in.read(buffer, offset, left); offset = offset + numOfBytesRead; left = left - numOfBytesRead; } return buffer; }
Simile a readU8Array(int) , esistono metodi readU16int() e readU32int() che leggono byte dal socket e restituiscono un valore intero.
Dopo aver inviato la versione del protocollo e aver letto la risposta, il servizio RFB dovrebbe inviare un messaggio di sicurezza:
/* * RFB server sends security type bytes that may request * a user to type password. * In this implementation, this is set to simples * possible option: no authentication at all. */ sendSecurityType();
In questa implementazione, viene scelto il modo più semplice: non richiedere alcuna password dal lato client VNC.
private void sendSecurityType() throws IOException { out.write(SECURITY_TYPE); out.flush(); }
dove SECURITY_TYPE è array di byte:
private final byte[] SECURITY_TYPE = {0x00, 0x00, 0x00, 0x01};
Questa matrice di byte dal protocollo RFB versione 3.3 significa che il visualizzatore VNC non ha bisogno di inviare alcuna password.
Successivamente, ciò che il servizio RFB dovrebbe ottenere dal client è il flag del desktop condiviso. È un byte sul socket.
/* * RFB server reads shared desktop flag. It's a single * byte that tells RFB server * should it support multiple VNC viewers connected at * same time or not. */ byte sharedDesktop = readSharedDesktop();
Una volta che il flag del desktop condiviso viene letto dal socket, lo ignoriamo nella nostra implementazione.
Il servizio RFB deve inviare un messaggio di inizializzazione del server:
/* * RFB server sends ServerInit message that includes * screen resolution, * number of colors, depth, screen title, etc. */ screenWidth = JFrameMainWindow.jFrameMainWindow.getWidth(); screenHeight = JFrameMainWindow.jFrameMainWindow.getHeight(); String windowTitle = JFrameMainWindow.jFrameMainWindow.getTitle(); sendServerInit(screenWidth, screenHeight, windowTitle);
La classe JFrameMainWindow è JFrame, che è qui a scopo dimostrativo come fonte di grafica. Il messaggio di inizializzazione del server ha la larghezza e l'altezza dello schermo obbligatorie in pixel e il titolo del desktop. In questo esempio è il titolo di JFrame ottenuto dal metodo getTitle().
Dopo il messaggio di inizializzazione del server, il thread del servizio RFB esegue il loop leggendo dal socket sei tipi di messaggi:
/* * Main loop where clients messages are read from socket. */ while (true) { /* * Mark first byte and read it. */ in.mark(1); int messageType = in.read(); if (messageType == -1) { break; } /* * Go one byte back. */ in.reset(); /* * Depending on message type, read complete message on socket. */ if (messageType == 0) { /* * Set Pixel Format */ readSetPixelFormat(); } else if (messageType == 2) { /* * Set Encodings */ readSetEncoding(); } else if (messageType == 3) { /* * Frame Buffer Update Request */ readFrameBufferUpdateRequest(); } else if (messageType == 4) { /* * Key Event */ readKeyEvent(); } else if (messageType == 5) { /* * Pointer Event */ readPointerEvent(); } else if (messageType == 6) { /* * Client Cut Text */ readClientCutText(); } else { err("Unknown message type. Received message type = " + messageType); } }
Ogni metodo readSetPixelFormat() , readSetEncoding() , readFrameBufferUpdateRequest() , … readClientCutText() sta bloccando e attiva alcune azioni.
Ad esempio, il metodo readClientCutText() legge il testo codificato nel messaggio quando l'utente taglia il testo sul lato client e quindi il visualizzatore VNC invia il testo tramite il protocollo RFB al server. Il testo viene quindi posizionato sul lato server negli Appunti.

Messaggi del cliente
Tutti e sei i messaggi devono essere supportati dal servizio RFB, almeno a livello di byte: quando il client invia il messaggio, deve essere letta una lunghezza di byte completa. Questo perché il protocollo RFB è orientato ai byte e non c'è limite tra due messaggi.
Il messaggio più importante è la richiesta di aggiornamento del frame buffer. Il cliente può richiedere l'aggiornamento completo o l'aggiornamento incrementale dello schermo.
private void readFrameBufferUpdateRequest() throws IOException { int messageType = in.read(); int incremental = in.read(); if (messageType == 0x03) { int x_pos = readU16int(); int y_pos = readU16int(); int width = readU16int(); int height = readU16int(); screenWidth = width; screenHeight = height; if (incremental == 0x00) { incrementalFrameBufferUpdate = false; int x = JFrameMainWindow.jFrameMainWindow.getX(); int y = JFrameMainWindow.jFrameMainWindow.getY(); RobotScreen.robo.getScreenshot(x, y, width, height); sendFrameBufferUpdate(x_pos, y_pos, width, height, 0, RobotScreen.robo.getColorImageBuffer()); } else if (incremental == 0x01) { incrementalFrameBufferUpdate = true; } else { throw new IOException(); } } else { throw new IOException(); } }
Il primo byte del messaggio di richiesta del frame buffer è il tipo di messaggio. Il valore è sempre 0x03. Il byte successivo è il flag incrementale, che dice al server di inviare full frame o solo una differenza. In caso di richiesta di aggiornamento completo, il servizio RFB acquisirà uno screenshot della finestra principale utilizzando la classe RobotScreen e lo invierà al client.
Se si tratta di una richiesta incrementale, un flag incrementalFrameBufferUpdate verrà impostato su true. Questo flag verrà utilizzato dai componenti Swing per verificare se devono inviare parti dello schermo che sono state modificate. Di solito JMenu, JMenuItem, JTextArea, ecc. devono eseguire un aggiornamento incrementale dello schermo quando l'utente sposta il puntatore del mouse, fa clic, invia una sequenza di tasti, ecc.
Il metodo sendFrameBufferUpdate(int, int, int, int, int[]) scarica il buffer dell'immagine nel socket.
public void sendFrameBufferUpdate(int x, int y, int width, int height, int encodingType, int[] screen) throws IOException { if (x + width > screenWidth || y + height > screenHeight) { err ("Invalid frame update size:"); err (" x = " + x + ", y = " + y); err (" width = " + width + ", height = " + height); return; } byte messageType = 0x00; byte padding = 0x00; out.write(messageType); out.write(padding); int numberOfRectangles = 1; writeU16int(numberOfRectangles); writeU16int(x); writeU16int(y); writeU16int(width); writeU16int(height); writeS32int(encodingType); for (int rgbValue : screen) { int red = (rgbValue & 0x000000FF); int green = (rgbValue & 0x0000FF00) >> 8; int blue = (rgbValue & 0x00FF0000) >> 16; if (bits_per_pixel == 8) { out.write((byte) colorMap.get8bitPixelValue(red, green, blue)); } else { out.write(red); out.write(green); out.write(blue); out.write(0); } } out.flush(); }
Il metodo verifica che le coordinate (x, y) non escano dallo schermo insieme alla larghezza x altezza del buffer dell'immagine. Il valore del tipo di messaggio per l'aggiornamento del frame buffer è 0x00. Il valore di riempimento è solitamente 0x00 e dovrebbe essere ignorato dal visualizzatore VNC. Il numero di rettangoli è un valore di due byte e definisce quanti rettangoli seguono nel messaggio.
Ogni rettangolo ha coordinate in alto a sinistra, larghezza e altezza, tipo di codifica e dati pixel. Esistono alcuni formati di codifica efficienti che possono essere utilizzati, come zrle, hextile e tight. Tuttavia, per mantenere le cose semplici e facili da capire, utilizzeremo la codifica grezza nella nostra implementazione.
La codifica grezza significa che il colore dei pixel viene trasmesso come componente RGB. Se il client ha impostato la codifica dei pixel su 32 bit, vengono trasmessi 4 byte per ogni pixel. Se il client utilizza la modalità colore a 8 bit, ogni pixel viene trasmesso come 1 byte. Il codice è mostrato nel ciclo for. Si noti che per la modalità a 8 bit la mappa dei colori viene utilizzata per trovare la migliore corrispondenza per ciascun pixel dallo screenshot / buffer dell'immagine. Per la modalità pixel a 32 bit, il buffer dell'immagine contiene una matrice di numeri interi, ogni valore ha componenti RGB multiplexati.
Applicazione di dimostrazione dell'oscillazione
L'applicazione demo Swing contiene un listener di azioni che attiva il metodo sendFrameBufferUpdate(int, int, int, int, int[]) . Di solito gli elementi dell'applicazione, come i componenti Swing, dovrebbero avere listener e inviare modifiche allo schermo al client. Ad esempio quando l'utente digita qualcosa in JTextArea, dovrebbe essere trasmesso al visualizzatore VNC.
public void actionPerformed(ActionEvent arg0) { /* * Get dimensions and location of main JFrame window. */ int offsetX = JFrameMainWindow.jFrameMainWindow.getX(); int offsetY = JFrameMainWindow.jFrameMainWindow.getY(); int width = JFrameMainWindow.jFrameMainWindow.getWidth(); int height = JFrameMainWindow.jFrameMainWindow.getHeight(); /* * Do not update screen if main window dimension has changed. * Upon main window resize, another action listener will * take action. */ int screenWidth = RFBDemo.rfbClientList.get(0).screenWidth; int screenHeight = RFBDemo.rfbClientList.get(0).screenHeight; if (width != screenWidth || height != screenHeight) { return; } /* * Capture new screenshot into image buffer. */ RobotScreen.robo.getScreenshot(offsetX, offsetY, width, height); int[] delta = RobotScreen.robo.getDeltaImageBuffer(); if (delta == null) { offsetX = 0; offsetY = 0; Iterator<RFBService> it = RFBDemo.rfbClientList.iterator(); while (it.hasNext()) { RFBService rfbClient = it.next(); if (rfbClient.incrementalFrameBufferUpdate) { try { /* * Send complete window. */ rfbClient.sendFrameBufferUpdate( offsetX, offsetY, width, height, 0, RobotScreen.robo.getColorImageBuffer()); } catch (SocketException ex) { it.remove(); } catch (IOException ex) { ex.printStackTrace(); it.remove(); } rfbClient.incrementalFrameBufferUpdate = false; } } } else { offsetX = RobotScreen.robo.getDeltaX(); offsetY = RobotScreen.robo.getDeltaY(); width = RobotScreen.robo.getDeltaWidth(); height = RobotScreen.robo.getDeltaHeight(); Iterator<RFBService> it = RFBDemo.rfbClientList.iterator(); while (it.hasNext()) { RFBService rfbClient = it.next(); if (rfbClient.incrementalFrameBufferUpdate) { try { /* * Send only delta rectangle. */ rfbClient.sendFrameBufferUpdate( offsetX, offsetY, width, height, 0, delta); } catch (SocketException ex) { it.remove(); } catch (IOException ex) { ex.printStackTrace(); it.remove(); } rfbClient.incrementalFrameBufferUpdate = false; } } } }
Il codice di questo listener di azioni è piuttosto semplice: prende uno screenshot della finestra principale JFrameMain usando la classe RobotScreen, quindi viene determinato se è necessario un aggiornamento parziale dello schermo. La variabile diffUpdateOfScreen viene utilizzata come flag per l'aggiornamento parziale. E infine il buffer dell'immagine completo o solo righe diverse vengono trasmesse al client. Questo codice considera anche più client connessi, ecco perché viene utilizzato l'iteratore e l'elenco dei client viene mantenuto nel membro RFBDemo.rfbClientList<RFBService> .
Il listener di azioni di aggiornamento Framebuffer può essere utilizzato in Timer che può essere avviato da qualsiasi modifica JComponent:
/* * Define timer for frame buffer update with 400 ms delay and * no repeat. */ timerUpdateFrameBuffer = new Timer(400, new ActionListenerFrameBufferUpdate()); timerUpdateFrameBuffer.setRepeats(false);
Questo codice è nel costruttore della classe JFrameMainWindow. Il timer viene avviato nel metodo doIncrementalFrameBufferUpdate():
public void doIncrementalFrameBufferUpdate() { if (RFBDemo.rfbClientList.size() == 0) { return; } if (!timerUpdateFrameBuffer.isRunning()) { timerUpdateFrameBuffer.start(); } }
Altri listener di azioni di solito chiamano il metodo doIncrementalFrameBufferUpdate():
public class DocumentListenerChange implements DocumentListener { @Override public void changedUpdate(DocumentEvent e) { JFrameMainWindow jFrameMainWindow = JFrameMainWindow.jFrameMainWindow; jFrameMainWindow.doIncrementalFrameBufferUpdate(); } // ... }
In questo modo dovrebbe essere semplice e facile da seguire. È necessario solo un riferimento all'istanza JFrameMainWindow e una singola chiamata al metodo doIncrementalFrameBufferUpdate() . Il metodo verificherà se ci sono client connessi e, in tal caso, verrà avviato il timer timerUpdateFrameBuffer . Una volta avviato il timer, il listener di azioni acquisirà effettivamente uno screenshot e sendFrameBufferUpdate() verrà eseguito.
La figura sopra mostra la relazione del listener con la procedura di aggiornamento del frame buffer. La maggior parte dei listener viene attivata quando l'utente esegue un'azione: fa clic, seleziona il testo, digita qualcosa nell'area di testo, ecc. Quindi viene eseguita la funzione membro doIncrementalFramebufferUpdate() che avvia il timer timerUpdateFrameBuffer . Il timer alla fine chiamerà il metodo sendFrameBufferUpdate() nella classe RFBService e causerà l'aggiornamento dello schermo sul lato client (visualizzatore VNC).
Cattura schermo, riproduci sequenze di tasti e sposta il puntatore del mouse sullo schermo
Java ha una classe Robot incorporata che consente allo sviluppatore di scrivere un'applicazione che acquisirà schermate, invierà chiavi, manipolerà il puntatore del mouse, produrrà clic, ecc.
Per afferrare l'area dello schermo in cui è visualizzata la finestra JFrame, viene utilizzato RobotScreen. Il metodo principale è getScreenshot(int, int, int, int) che cattura una regione dello schermo. I valori RGB per ogni pixel sono memorizzati in un array int[]:
public void getScreenshot(int x, int y, int width, int height) { Rectangle screenRect = new Rectangle(x, y, width, height); BufferedImage colorImage = robot.createScreenCapture(screenRect); previousImageBuffer = colorImageBuffer; colorImageBuffer = ((DataBufferInt) colorImage.getRaster().getDataBuffer()).getData(); if (previousImageBuffer == null || previousImageBuffer.length != colorImageBuffer.length) { previousImageBuffer = colorImageBuffer; } this.width = width; this.height = height; }
Il metodo memorizza i pixel nell'array colorImageBuffer. Per ottenere i dati sui pixel, è possibile utilizzare il metodo getColorImageBuffer() .
Il metodo salva anche il buffer dell'immagine precedente. È possibile ottenere solo i pixel che sono stati modificati. Per ottenere solo la differenza dell'area dell'immagine, utilizzare il metodo getDeltaImageBuffer() .
L'invio di sequenze di tasti al sistema è facile con la classe Robot. Tuttavia, alcuni codici chiave speciali ricevuti dai visualizzatori VNC devono prima essere tradotti correttamente. La classe RobotKeyboard ha il metodo sendKey(int, int) che gestisce chiavi speciali e chiavi alfanumeriche:
public void sendKey(int keyCode, int state) { switch (keyCode) { case 0xff08: doType(VK_BACK_SPACE, state); break; case 0xff09: doType(VK_TAB, state); break; case 0xff0d: case 0xff8d: doType(VK_ENTER, state); break; case 0xff1b: doType(VK_ESCAPE, state); break; … case 0xffe1: case 0xffe2: doType(VK_SHIFT, state); break; case 0xffe3: case 0xffe4: doType(VK_CONTROL, state); break; case 0xffe9: case 0xffea: doType(VK_ALT, state); break; default: /* * Translation of a..z keys. */ if (keyCode >= 97 && keyCode <= 122) { /* * Turn lower-case a..z key codes into upper-case A..Z key codes. */ keyCode = keyCode - 32; } doType(keyCode, state); } }
Lo stato dell'argomento determina se il tasto viene premuto o rilasciato. Dopo la corretta traduzione del codice chiave nella costante VT, il metodo doType(int, int) passa il valore della chiave a Robot e l'effetto è lo stesso dell'utente locale che ha premuto il tasto sulla tastiera:
private void doType(int keyCode, int state) { if (state == 0) { robot.keyRelease(keyCode); } else { robot.keyPress(keyCode); } }
Simile a RobotKeyboard è la classe RobotMouse che gestisce gli eventi del puntatore e fa muovere e fare clic sul puntatore del mouse.
public void mouseMove(int x, int y) { robot.mouseMove(x, y); }
Tutte e tre le classi RobotScreen, RobotMouse e RobotKeyboard allocano una nuova istanza Robot nel costruttore:
this.robot = new Robot();
Abbiamo solo un'istanza di ciascuno, poiché non è necessario a livello di applicazione avere più di un'istanza della classe RobotScreen, RobotMouse o RobotKeyboard.
public static void main(String[] args) { ... /* * Initialize static Robot objects for screen, keyboard and mouse. */ RobotScreen.robo = new RobotScreen(); RobotKeyboard.robo = new RobotKeyboard(); RobotMouse.robo = new RobotMouse(); ... }
In questa applicazione demo queste istanze vengono create nella funzione main() .
Il risultato è un'applicazione basata su Swing in Java che funge da provider di servizi RFB e consente ai visualizzatori VNC standard di connettersi ad essa:
Conclusione
Il protocollo RFB è ampiamente utilizzato e accettato. Esistono implementazioni client sotto forma di visualizzatori VNC per quasi tutte le piattaforme e i dispositivi. Lo scopo principale è visualizzare in remoto i desktop, ma possono esserci anche altre applicazioni. Ad esempio, puoi creare ingegnosi strumenti grafici e accedervi da remoto per migliorare i flussi di lavoro remoti esistenti.
Questo articolo illustra le basi del protocollo RFB, il formato dei messaggi, come inviare parte dello schermo e come gestire tastiera e mouse. Il codice sorgente completo con l'applicazione demo Swing è disponibile su GitHub.