Implémentation d'un serveur Framebuffer distant en Java

Publié: 2022-03-11

En informatique, Virtual Network Computing (VNC) est un système de partage de bureau graphique qui utilise le protocole Remote Framebuffer (RFB) pour contrôler à distance un autre ordinateur. Il transmet les événements du clavier et de la souris d'un ordinateur à un autre et relaie les mises à jour de l'écran graphique dans l'autre sens sur un réseau.

RFB est un protocole simple d'accès à distance aux interfaces utilisateur graphiques. Parce qu'il fonctionne au niveau du tampon de trame, il est applicable à tous les systèmes et applications de fenêtrage, y compris Microsoft Windows, Mac OS X et X Window System.

Construire une application Swing alimentée par le protocole côté serveur Remote Framebuffer en Java

Construire une application Swing alimentée par le protocole côté serveur Remote Framebuffer en Java
Tweeter

Dans cet article, je vais montrer comment implémenter le protocole côté serveur RFB et démontrer avec une petite application Java Swing comment transmettre la fenêtre principale via une connexion TCP aux visualiseurs VNC. L'idée est de démontrer les fonctionnalités de base du protocole et leur implémentation possible en Java.

Le lecteur doit avoir une connaissance de base du langage de programmation Java et doit être familiarisé avec les concepts de base de la mise en réseau TCP/IP, du modèle client-serveur, etc. Idéalement, le lecteur est un développeur Java et a une certaine expérience des implémentations VNC bien connues telles que RealVNC , UltraVNC, TightVNC, etc.

Spécification du protocole Framebuffer distant

La spécification du protocole RFB est assez bien définie. Selon Wikipedia, le protocole RFB a plusieurs versions. Pour cet article, nous nous concentrerons sur les messages courants qui doivent être correctement compris par la plupart des implémentations VNC, quelle que soit la version du protocole.

Après qu'un visualiseur VNC (client) a établi une connexion TCP vers un serveur VNC (service RFB), la première phase implique l'échange de la version du protocole :

 RFB Service ----------- "RFB 003.003\n" -------> VNC viewer RFB Service <---------- "RFB 003.008\n" -------- VNC viewer

C'est un simple flux d'octets qui peut être décodé en caractères ASCII, comme "RFB 003.008\n".

Une fois cela fait, la prochaine étape est l'authentification. Le serveur VNC envoie un tableau d'octets pour indiquer le type d'authentifications qu'il prend en charge. Par exemple:

 RFB Service ----------- 0x01 0x02 -----------> VNC viewer RFB Service <----------- 0x02 ----------- VNC viewer

Ici, le serveur VNC n'a envoyé qu'un seul type d'authentification possible (0x02). Le premier octet 0x01 indique le nombre de types d'authentification disponibles. Le visualiseur VNC doit répondre avec la valeur 0x02, car c'est le seul type possible pris en charge par le serveur dans cet exemple.

Ensuite, le serveur enverra un défi d'authentification (selon l'algorithme, il y en a plusieurs), et le client doit répondre avec un message de réponse de défi approprié et attendre que le serveur confirme la réponse. Une fois que le client est authentifié, il peut poursuivre le processus d'établissement de session.

Le moyen le plus simple ici est de ne choisir aucune authentification. Le protocole RFB n'est de toute façon pas sécurisé, quel que soit le mécanisme d'authentification. Si la sécurité est importante, la bonne façon serait de tunnelliser les sessions RFB via des connexions VPN ou SSH.

À ce stade, la visionneuse VNC envoie un message de bureau partagé qui indique si le client partagera et autorisera d'autres visionneuses VNC à se connecter au même bureau. Il appartient à l'implémentation du service RFB de prendre en compte ce message et éventuellement d'empêcher plusieurs visualiseurs VNC de partager le même écran. Ce message n'a qu'une longueur de 1 octet et une valeur valide est 0x00 ou 0x01.

Enfin, le serveur RFB envoie un message d'initialisation du serveur, qui contient la dimension de l'écran, les bits par pixel, la profondeur, le drapeau big endian et les drapeaux de vraies couleurs, les valeurs maximales pour les couleurs rouge, vert et bleu, les positions des bits en pixel pour les couleurs rouge, vert et bleu. , et chaîne/titre du bureau. Les deux premiers octets représentent la largeur de l'écran en pixels, les deux octets suivants la hauteur de l'écran. Après les octets de hauteur d'écran, les bits par octet de pixel doivent être présents dans le message. La valeur est généralement 8, 16 ou 32. Sur la plupart des systèmes modernes avec une gamme de couleurs complète, les bits par pixel octet ont la valeur 32 (0x20). Il indique au client qu'il peut demander une couleur complète pour chaque pixel au serveur. L'octet big endian n'est pas nul uniquement si les pixels sont dans l'ordre big endian. Si l'octet de couleur vraie est différent de zéro (vrai), les six octets suivants spécifient comment extraire les intensités de couleur rouge, verte et bleue de la valeur du pixel. Les six octets suivants sont les valeurs maximales autorisées pour les composants rouge, vert et bleu du pixel. Ceci est important en mode couleur 8 bits, où seuls quelques bits sont disponibles pour chaque composant de couleur. Les décalages vers le rouge, le vert et le bleu déterminent les positions des bits pour chaque couleur. Les trois derniers octets sont un remplissage et doivent être ignorés par le client. Après le format pixel, il y a un octet qui définit la longueur d'une chaîne pour le titre du bureau. Le titre du bureau est une chaîne codée en ASCII dans un tableau d'octets de longueur arbitraire.

Protocole serveur-client Remote Framebuffer : échange de version, authentification et message d'initialisation du serveur

Protocole serveur-client Remote Framebuffer : échange de version, authentification et message d'initialisation du serveur
Tweeter

Après le message d'initialisation du serveur, le service RFB doit lire les messages du client à partir du socket et les décoder. Il existe 6 types de messages :

  • Définir le format des pixels
  • SetEncodings
  • FramebufferUpdateRequest
  • L'évenement important
  • PointerEvent
  • ClientCutText

La documentation du protocole est assez précise et explique chaque message. Pour chaque message, chaque octet est expliqué. Par exemple, message d'initialisation du serveur :

Nombre d'octets Taper La description
2 U16 framebuffer-largeur
2 U16 framebuffer-hauteur
16 PIXEL_FORMAT format de pixel du serveur
4 U32 nom-longueur
nom-longueur Tableau U8 nom-chaîne

Ici, PIXEL_FORMAT est :

Nombre d'octets Taper La description
1 U8 bits par pixel
1 U8 profondeur
1 U8 drapeau big-endian
1 U8 vrai-couleur-drapeau
2 U16 rouge-max
2 U16 vert-max
2 U16 bleu-max
1 U8 décalage vers le rouge
1 U8 décalage vert
1 U8 décalage vers le bleu
3 rembourrage

U16 signifie entier 16 bits non signé (deux octets), U32 est un entier 32 bits non signé, U8 tableau est un tableau d'octets, etc.

Implémentation du protocole en Java

Une application serveur Java typique se compose d'un thread écoutant les connexions client et de plusieurs threads gérant les connexions 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(); }

Ici le port TCP 5902 a été choisi (affichage :2), et la boucle while attend qu'un client se connecte. La méthode ServerSocket.accept() est bloquante et fait attendre le thread pour une nouvelle connexion client. Une fois que le client se connecte, un nouveau thread RFBService est créé qui gère les messages de protocole RFB reçus du client.

La classe RFBService implémente l'interface Runnable. C'est plein de méthodes pour lire les octets de socket. La méthode run() est importante, elle est exécutée immédiatement lorsque le thread est lancé en fin de boucle :

 @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(); }

Ici, la méthode sendProtocolVersion() envoie la chaîne RFB au client (visualiseur VNC), puis lit la chaîne de version du protocole à partir du client. Le client doit répondre par quelque chose comme "RFB 003.008\n". La méthode readProtocolVersion() est bien sûr bloquante, comme toute méthode dont le nom commence par le mot read.

 private String readProtocolVersion() throws IOException { byte[] buffer = readU8Array(12); return new String(buffer); }

La méthode readProtocolVersion() est simple : elle lit 12 octets depuis socket et renvoie une valeur de chaîne. La fonction readU8Array(int) lit le nombre d'octets spécifié, dans ce cas 12 octets. S'il n'y a pas assez d'octets à lire sur le socket, il attend :

 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; }

Semblable à readU8Array(int) , il existe des méthodes readU16int() et readU32int() qui lisent les octets du socket et renvoient une valeur entière.

Après avoir envoyé la version du protocole et lu la réponse, le service RFB doit envoyer un message de sécurité :

 /* * 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();

Dans cette implémentation, le moyen le plus simple est choisi : ne pas exiger de mot de passe côté client VNC.

 private void sendSecurityType() throws IOException { out.write(SECURITY_TYPE); out.flush(); }

où SECURITY_TYPE est un tableau d'octets :

 private final byte[] SECURITY_TYPE = {0x00, 0x00, 0x00, 0x01};

Ce tableau d'octets par la version 3.3 du protocole RFB signifie que le visualiseur VNC n'a pas besoin d'envoyer de mot de passe.

Ensuite, ce que le service RFB doit obtenir du client est l'indicateur de bureau partagé. C'est un octet sur 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();

Une fois que l'indicateur de bureau partagé est lu à partir du socket, nous l'ignorons dans notre implémentation.

Le service RFB doit envoyer un message d'initialisation du serveur :

 /* * 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 est JFrame, qui est ici à des fins de démonstration en tant que source de graphiques. Le message d'initialisation du serveur a une largeur et une hauteur d'écran obligatoires en pixels et un titre de bureau. Dans cet exemple, il s'agit du titre de JFrame obtenu par la méthode getTitle().

Après le message d'initialisation du serveur, le thread de service RFB boucle en lisant à partir du socket six types de messages :

 /* * 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); } }

Chaque méthode readSetPixelFormat() , readSetEncoding() , readFrameBufferUpdateRequest() , … readClientCutText() est bloquante et déclenche une action.

Par exemple, la méthode readClientCutText() lit le texte qui est encodé dans le message lorsque l'utilisateur coupe le texte côté client, puis le visualiseur VNC envoie le texte via le protocole RFB au serveur. Le texte est ensuite placé côté serveur dans le Presse-papiers.

Messages clients

Les six messages doivent être pris en charge par le service RFB, au moins au niveau des octets : lorsque le client envoie un message, un octet complet doit être lu. En effet, le protocole RFB est orienté octet et il n'y a pas de frontière entre deux messages.

Le message le plus important est la demande de mise à jour du tampon de trame. Le client peut demander une mise à jour complète ou une mise à jour incrémentielle de l'écran.

 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(); } }

Le premier octet du message de demande de tampon de trame est le type de message. La valeur est toujours 0x03. L'octet suivant est un indicateur incrémentiel, qui indique au serveur d'envoyer une trame complète ou juste une différence. En cas de demande de mise à jour complète, le service RFB prendra une capture d'écran de la fenêtre principale à l'aide de la classe RobotScreen et l'enverra au client.

S'il s'agit d'une demande incrémentielle, un indicateur incrementalFrameBufferUpdate sera défini sur true. Ce drapeau sera utilisé par les composants Swing pour vérifier s'ils doivent envoyer des parties d'écran qui ont changé. Habituellement, JMenu, JMenuItem, JTextArea, etc. doivent effectuer une mise à jour incrémentielle de l'écran lorsque l'utilisateur déplace le pointeur de la souris, clique, envoie une frappe, etc.

La méthode sendFrameBufferUpdate(int, int, int, int, int[]) vide le tampon d'image vers le 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(); }

La méthode vérifie que les coordonnées (x, y) ne sortent pas de l'écran avec la largeur x la hauteur du tampon d'image. La valeur du type de message pour la mise à jour du tampon de trame est 0x00. La valeur de rembourrage est généralement 0x00 et doit être ignorée par le visualiseur VNC. Le nombre de rectangles est une valeur de deux octets et définit le nombre de rectangles qui suivent dans le message.

Chaque rectangle a une coordonnée supérieure gauche, une largeur et une hauteur, un type d'encodage et des données de pixel. Certains formats d'encodage efficaces peuvent être utilisés, tels que zrle, hextile et tight. Cependant, pour garder les choses simples et faciles à comprendre, nous utiliserons l'encodage brut dans notre implémentation.

Le codage brut signifie que la couleur des pixels est transmise en tant que composante RVB. Si le client a défini le codage de pixel sur 32 bits, 4 octets sont transmis pour chaque pixel. Si le client utilise le mode couleur 8 bits, chaque pixel est transmis sur 1 octet. Le code est affiché dans la boucle for. Notez que pour le mode 8 bits, la carte des couleurs est utilisée pour trouver la meilleure correspondance pour chaque pixel à partir de la capture d'écran / du tampon d'image. Pour le mode pixel 32 bits, le tampon d'image contient un tableau d'entiers, chaque valeur a des composants RVB multiplexés.

Application de démonstration Swing

L'application de démonstration Swing contient un écouteur d'action qui déclenche la méthode sendFrameBufferUpdate(int, int, int, int, int[]) . Habituellement, les éléments d'application, comme les composants Swing, doivent avoir des écouteurs et envoyer le changement d'écran au client. Par exemple, lorsque l'utilisateur tape quelque chose dans JTextArea, il doit être transmis au visualiseur 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; } } } }

Le code de cet écouteur d'action est assez simple : il prend une capture d'écran de la fenêtre principale JFrameMain à l'aide de la classe RobotScreen, puis il est déterminé si une mise à jour partielle de l'écran est nécessaire. La variable diffUpdateOfScreen est utilisée comme indicateur pour la mise à jour partielle. Et enfin le tampon d'image complet ou seules les différentes lignes sont transmises au client. Ce code considère également plus de clients connectés, c'est pourquoi l'itérateur est utilisé et la liste des clients est maintenue dans le membre RFBDemo.rfbClientList<RFBService> .

L'écouteur d'action de mise à jour de Framebuffer peut être utilisé dans Timer qui peut être démarré par n'importe quel changement de JComponent :

 /* * Define timer for frame buffer update with 400 ms delay and * no repeat. */ timerUpdateFrameBuffer = new Timer(400, new ActionListenerFrameBufferUpdate()); timerUpdateFrameBuffer.setRepeats(false);

Ce code est dans le constructeur de la classe JFrameMainWindow. Le minuteur est démarré dans la méthode doIncrementalFrameBufferUpdate() :

 public void doIncrementalFrameBufferUpdate() { if (RFBDemo.rfbClientList.size() == 0) { return; } if (!timerUpdateFrameBuffer.isRunning()) { timerUpdateFrameBuffer.start(); } }

D'autres écouteurs d'action appellent généralement la méthode doIncrementalFrameBufferUpdate() :

 public class DocumentListenerChange implements DocumentListener { @Override public void changedUpdate(DocumentEvent e) { JFrameMainWindow jFrameMainWindow = JFrameMainWindow.jFrameMainWindow; jFrameMainWindow.doIncrementalFrameBufferUpdate(); } // ... }

Cette méthode doit être simple et facile à suivre. Seule une référence à l'instance JFrameMainWindow est nécessaire et un seul appel de la méthode doIncrementalFrameBufferUpdate() . La méthode vérifiera s'il y a des clients connectés, et s'il y en a, le minuteur timerUpdateFrameBuffer sera démarré. Une fois la minuterie démarrée, l'écouteur d'action prendra une capture d'écran et sendFrameBufferUpdate() sera exécuté.

La figure ci-dessus montre la relation entre l'écouteur et la procédure de mise à jour du tampon de trame. La plupart des écouteurs sont déclenchés lorsque l'utilisateur effectue une action : clique, sélectionne du texte, tape quelque chose dans la zone de texte, etc. Ensuite, la fonction membre doIncrementalFramebufferUpdate() est exécutée, ce qui démarre le minuteur timerUpdateFrameBuffer . Le minuteur appellera éventuellement la méthode sendFrameBufferUpdate() dans la classe RFBService et provoquera une mise à jour de l'écran côté client (visualiseur VNC).

Capturez l'écran, jouez les frappes et déplacez le pointeur de la souris sur l'écran

Java a une classe Robot intégrée qui permet au développeur d'écrire une application qui saisira des captures d'écran, enverra des clés, manipulera le pointeur de la souris, produira des clics, etc.

Pour saisir la zone de l'écran où la fenêtre JFrame est affichée, RobotScreen est utilisé. La méthode principale est getScreenshot(int, int, int, int) qui capture une région de l'écran. Les valeurs RVB de chaque pixel sont stockées dans un tableau 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; }

La méthode stocke les pixels dans le tableau colorImageBuffer. Pour obtenir des données de pixels, la méthode getColorImageBuffer() peut être utilisée.

La méthode enregistre également le tampon d'image précédent. Il est possible d'obtenir uniquement les pixels qui ont été modifiés. Pour obtenir uniquement la différence de zone d'image, utilisez la méthode getDeltaImageBuffer() .

L'envoi de frappes au système est facile avec la classe Robot. Cependant, certains codes clés spéciaux reçus des visualiseurs VNC doivent d'abord être traduits correctement. La classe RobotKeyboard a la méthode sendKey(int, int) qui gère les clés spéciales et les clés alphanumériques :

 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); } }

L'état de l'argument détermine si la touche est enfoncée ou relâchée. Après la traduction correcte du code de touche en constante VT, la méthode doType(int, int) transmet la valeur de la touche à Robot et l'effet est le même que l'utilisateur local a appuyé sur la touche du clavier :

 private void doType(int keyCode, int state) { if (state == 0) { robot.keyRelease(keyCode); } else { robot.keyPress(keyCode); } }

Semblable à RobotKeyboard, la classe RobotMouse gère les événements du pointeur et provoque le déplacement et le clic du pointeur de la souris.

 public void mouseMove(int x, int y) { robot.mouseMove(x, y); }

Les trois classes RobotScreen, RobotMouse et RobotKeyboard allouent une nouvelle instance Robot dans le constructeur :

 this.robot = new Robot();

Nous n'avons qu'une seule instance de chacun, car il n'est pas nécessaire au niveau de l'application d'avoir plus d'une instance de la classe RobotScreen, RobotMouse ou 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(); ... }

Dans cette application de démonstration, ces instances sont créées dans la fonction main() .

Le résultat est une application basée sur Swing en Java qui agit comme un fournisseur de services RFB et permet aux visualiseurs VNC standard de s'y connecter :

Conclusion

Le protocole RFB est largement utilisé et accepté. Des implémentations client sous forme de visualiseurs VNC existent pour presque toutes les plates-formes et tous les appareils. L'objectif principal est d'afficher à distance les bureaux, mais il peut également y avoir d'autres applications. Par exemple, vous pouvez créer des outils graphiques astucieux et y accéder à distance pour améliorer vos flux de travail à distance existants.

Cet article couvre les bases du protocole RFB, le format des messages, comment envoyer une partie de l'écran et comment gérer le clavier et la souris. Le code source complet avec l'application de démonstration Swing est disponible sur GitHub.