Implementando um servidor remoto Framebuffer em Java

Publicados: 2022-03-11

Na computação, Virtual Network Computing (VNC) é um sistema gráfico de compartilhamento de área de trabalho que usa o protocolo Remote Framebuffer (RFB) para controlar remotamente outro computador. Ele transmite eventos de teclado e mouse de um computador para outro e retransmite atualizações de tela gráfica de volta na outra direção por meio de uma rede.

RFB é um protocolo simples para acesso remoto a interfaces gráficas de usuário. Como funciona no nível do buffer de quadro, é aplicável a todos os sistemas e aplicativos de janelas, incluindo Microsoft Windows, Mac OS X e X Window System.

Construindo um aplicativo Swing alimentado por protocolo remoto do lado do servidor Framebuffer em Java

Construindo um aplicativo Swing alimentado por protocolo remoto do lado do servidor Framebuffer em Java
Tweet

Neste artigo, mostrarei como implementar o protocolo RFB do lado do servidor e demonstrar com um pequeno aplicativo Java Swing como transmitir a janela principal pela conexão TCP para visualizadores VNC. A ideia é demonstrar características básicas do protocolo e possível implementação em Java.

O leitor deve ter conhecimento básico da linguagem de programação Java e deve estar familiarizado com conceitos básicos de rede TCP/IP, modelo cliente-servidor, etc. Idealmente, o leitor é um desenvolvedor Java e tem alguma experiência com implementações VNC conhecidas, como RealVNC , UltraVNC, TightVNC, etc.

Especificação do Protocolo de Framebuffer Remoto

A especificação do protocolo RFB é muito bem definida. Segundo a Wikipedia, o protocolo RFB possui várias versões. Para este artigo, nosso foco será nas mensagens comuns que devem ser entendidas corretamente pela maioria das implementações de VNC, independentemente da versão do protocolo.

Depois que um visualizador VNC (cliente) estabelece uma conexão TCP com um servidor VNC (serviço RFB), a primeira fase envolve a troca da versão do protocolo:

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

É um fluxo simples de bytes que pode ser decodificado em caracteres ASCII, como “RFB 003.008\n”.

Feito isso, o próximo passo é a autenticação. O servidor VNC envia uma matriz de bytes para indicar que tipo de autenticações ele suporta. Por exemplo:

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

Aqui o servidor VNC enviou apenas 1 tipo de autenticação possível (0x02). O primeiro byte 0x01 denota o número de tipos de autenticação disponíveis. O visualizador VNC deve responder com o valor 0x02, pois esse é o único tipo possível suportado pelo servidor neste exemplo.

Em seguida, o servidor enviará um desafio de autenticação (dependendo de qual algoritmo, existem vários), e o cliente deve responder com uma mensagem de resposta de desafio adequada e aguardar que o servidor confirme a resposta. Uma vez que o cliente é autenticado, ele pode continuar com o processo de estabelecimento da sessão.

A maneira mais simples aqui é não escolher nenhuma autenticação. O protocolo RFB é inseguro de qualquer maneira, independentemente do mecanismo de autenticação. Se a segurança for importante, a maneira correta seria encapsular sessões RFB por meio de conexões VPN ou SSH.

Neste ponto, o visualizador VNC envia uma mensagem de área de trabalho compartilhada que informa se o cliente compartilhará e permitirá que outros visualizadores VNC se conectem à mesma área de trabalho. Cabe à implementação do serviço RFB considerar essa mensagem e possivelmente impedir que vários visualizadores VNC compartilhem a mesma tela. Esta mensagem tem apenas 1 byte de comprimento e um valor válido é 0x00 ou 0x01.

Por fim, o servidor RFB envia uma mensagem de inicialização do servidor, que contém a dimensão da tela, bits por pixel, profundidade, bandeira big endian e bandeiras true color, valores máximos para cores vermelha, verde e azul, posições de bits em pixel para cores vermelha, verde e azul , e string/título da área de trabalho. Os primeiros dois bytes representam a largura da tela em pixels, os próximos dois bytes são a altura da tela. Após os bytes de altura da tela, bits por byte de pixel devem estar presentes na mensagem. O valor geralmente é 8, 16 ou 32. Na maioria dos sistemas modernos com faixa de cores completa, bits por byte de pixel tem valor 32 (0x20). Ele informa ao cliente que pode solicitar cores completas para cada pixel do servidor. O byte big endian é diferente de zero somente se os pixels estiverem na ordem big endian. Se o byte true color for diferente de zero (true), os próximos seis bytes especificam como extrair as intensidades das cores vermelha, verde e azul do valor do pixel. Os próximos seis bytes são os valores máximos permitidos para os componentes vermelho, verde e azul do pixel. Isso é importante no modo de cor de 8 bits, onde apenas alguns bits estão disponíveis para cada componente de cor. Os deslocamentos para vermelho, verde e azul determinam as posições dos bits para cada cor. Os últimos três bytes são preenchimento e devem ser ignorados pelo cliente. Após o formato de pixel, há um byte que define o comprimento de uma string para o título da área de trabalho. O título da área de trabalho é uma string codificada em ASCII em uma matriz de bytes de comprimento arbitrário.

Protocolo servidor-cliente do Framebuffer remoto: troca de versão, autenticação e mensagem de inicialização do servidor

Protocolo servidor-cliente do Framebuffer remoto: troca de versão, autenticação e mensagem de inicialização do servidor
Tweet

Após a mensagem de inicialização do servidor, o serviço RFB deve ler as mensagens do cliente do soquete e decodificá-las. Existem 6 tipos de mensagens:

  • SetPixelFormat
  • Definir Codificações
  • FramebufferUpdateRequest
  • Evento-chave
  • PointerEvent
  • ClientCutText

A documentação do protocolo é bastante exata e explica cada mensagem. Para cada mensagem, cada byte é explicado. Por exemplo, mensagem de inicialização do servidor:

Nº de bytes Tipo Descrição
2 U16 largura do buffer de quadro
2 U16 framebuffer-height
16 PIXEL_FORMAT formato de pixel do servidor
4 U32 nome-comprimento
nome-comprimento Matriz U8 seqüência de nomes

Aqui, PIXEL_FORMAT é:

Nº de bytes Tipo Descrição
1 U8 bits por pixel
1 U8 profundidade
1 U8 big-endian-bandeira
1 U8 bandeira de cor verdadeira
2 U16 máximo vermelho
2 U16 green-max
2 U16 azul-max
1 U8 desvio para o vermelho
1 U8 mudança verde
1 U8 turno azul
3 preenchimento

U16 significa inteiro sem sinal de 16 bits (dois bytes), U32 é inteiro sem sinal de 32 bits, matriz U8 é matriz de bytes, etc.

Implementação de protocolo em Java

Um aplicativo de servidor Java típico consiste em um encadeamento atendendo conexões de clientes e vários encadeamentos manipulando conexões de clientes.

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

Aqui a porta TCP 5902 foi escolhida (display :2), e o loop while espera que um cliente se conecte. O método ServerSocket.accept() está bloqueando e faz o thread esperar por uma nova conexão de cliente. Uma vez que o cliente se conecta, é criado um novo thread RFBService que trata as mensagens do protocolo RFB recebidas do cliente.

A classe RFBService implementa a interface Runnable. Está cheio de métodos para ler bytes do soquete. O método run() é importante, que é executado imediatamente quando a thread é iniciada no final do loop:

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

Aqui o método sendProtocolVersion() envia a string RFB para o cliente (visualizador VNC) e então lê a string da versão do protocolo do cliente. O cliente deve responder com algo como “RFB 003.008\n”. O método readProtocolVersion() é obviamente bloqueante, como qualquer método cujo nome comece com a palavra read.

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

O método readProtocolVersion() é simples: ele lê 12 bytes do socket e retorna um valor de string. A função readU8Array(int) lê o número especificado de bytes, neste caso 12 bytes. Se não houver bytes suficientes para ler no soquete, ele espera:

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

Semelhante a readU8Array(int) , existem métodos readU16int() e readU32int() que lêem bytes do socket e retornam um valor inteiro.

Após enviar a versão do protocolo e ler a resposta, o serviço RFB deve enviar mensagem de segurança:

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

Nesta implementação, a maneira mais simples é escolhida: não exigir nenhuma senha do lado do cliente VNC.

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

onde SECURITY_TYPE é uma matriz de bytes:

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

Essa matriz de bytes pelo protocolo RFB versão 3.3 significa que o visualizador VNC não precisa enviar nenhuma senha.

Em seguida, o que o serviço RFB deve obter do cliente é o sinalizador de área de trabalho compartilhada. É um byte no soquete.

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

Uma vez que o sinalizador de área de trabalho compartilhada é lido do soquete, nós o ignoramos em nossa implementação.

O serviço RFB deve enviar a mensagem de inicialização do servidor:

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

A classe JFrameMainWindow é JFrame, que está aqui para fins de demonstração como fonte de gráficos. A mensagem de inicialização do servidor tem largura e altura da tela obrigatória em pixels e título da área de trabalho. Neste exemplo é o título do JFrame obtido pelo método getTitle().

Após a mensagem de inicialização do servidor, o thread de serviço RFB faz um loop lendo do soquete seis tipos de mensagens:

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

Cada método readSetPixelFormat() , readSetEncoding() , readFrameBufferUpdateRequest() , … readClientCutText() está bloqueando e aciona alguma ação.

Por exemplo, o método readClientCutText() lê o texto que é codificado na mensagem quando o usuário corta o texto no lado do cliente e, em seguida, o visualizador VNC envia o texto via protocolo RFB para o servidor. O texto é então colocado no lado do servidor na área de transferência.

Mensagens do cliente

Todas as seis mensagens devem ser suportadas pelo serviço RFB, pelo menos no nível de byte: quando o cliente envia uma mensagem, um comprimento de byte completo deve ser lido. Isso ocorre porque o protocolo RFB é orientado a byte e não há limite entre duas mensagens.

A mensagem mais importante é a solicitação de atualização do buffer de quadro. O cliente pode solicitar atualização completa ou atualização incremental da tela.

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

O primeiro byte da mensagem de solicitação do buffer de quadro é do tipo de mensagem. O valor é sempre 0x03. O próximo byte é o sinalizador incremental, que informa ao servidor para enviar um quadro completo ou apenas uma diferença. Em caso de solicitação de atualização completa, o serviço RFB fará uma captura de tela da janela principal usando a classe RobotScreen e a enviará ao cliente.

Se for uma solicitação incremental, um sinalizador incrementalFrameBufferUpdate será definido como verdadeiro. Este sinalizador será usado pelos componentes Swing para verificar se eles precisam enviar partes da tela que foram alteradas. Normalmente JMenu, JMenuItem, JTextArea, etc.

O método sendFrameBufferUpdate(int, int, int, int, int[]) libera o buffer de imagem para o soquete.

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

O método verifica se a coordenada (x, y) não sai da tela junto com a largura x altura do buffer de imagem. O valor do tipo de mensagem para atualização do buffer de quadro é 0x00. O valor de preenchimento geralmente é 0x00 e deve ser ignorado pelo visualizador VNC. O número de retângulos é um valor de dois bytes e define quantos retângulos estão seguindo na mensagem.

Cada retângulo tem coordenada superior esquerda, largura e altura, tipo de codificação e dados de pixel. Existem alguns formatos de codificação eficientes que podem ser usados, como zrle, hextile e tight. No entanto, para manter as coisas simples e fáceis de entender, usaremos a codificação bruta em nossa implementação.

A codificação bruta significa que a cor do pixel é transmitida como componente RGB. Se o cliente tiver definido a codificação de pixel como 32 bits, serão transmitidos 4 bytes para cada pixel. Se o cliente usar o modo de cor de 8 bits, cada pixel será transmitido como 1 byte. O código é mostrado em loop for. Observe que, para o modo de 8 bits, o mapa de cores é usado para encontrar a melhor correspondência para cada pixel da captura de tela / buffer de imagem. Para o modo de pixel de 32 bits, o buffer de imagem contém uma matriz de inteiros, cada valor possui componentes RGB multiplexados.

Aplicativo de demonstração Swing

O aplicativo de demonstração Swing contém um ouvinte de ação que aciona o método sendFrameBufferUpdate(int, int, int, int, int[]) . Normalmente, os elementos da aplicação, como os componentes Swing, devem ter ouvintes e enviar a mudança de tela para o cliente. Como quando o usuário digita algo no JTextArea, ele deve ser transmitido para o visualizador 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; } } } }

O código deste action listener é bem simples: ele faz uma captura de tela da janela principal JFrameMain usando a classe RobotScreen, então é determinado se uma atualização parcial da tela é necessária. A variável diffUpdateOfScreen é usada como sinalizador para atualização parcial. E, finalmente, buffer de imagem completo ou apenas linhas diferentes são transmitidas ao cliente. Este código também considera mais clientes conectados, por isso o iterador é usado e a lista de clientes é mantida no membro RFBDemo.rfbClientList<RFBService> .

O ouvinte de ação de atualização do Framebuffer pode ser usado no Timer, que pode ser iniciado por qualquer alteração do JComponent:

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

Este código está no construtor da classe JFrameMainWindow. O temporizador é iniciado no método doIncrementalFrameBufferUpdate():

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

Outros ouvintes de ação geralmente chamam o método doIncrementalFrameBufferUpdate():

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

Este caminho deve ser simples e fácil de seguir. Apenas uma referência à instância JFrameMainWindow é necessária e uma única chamada do método doIncrementalFrameBufferUpdate() . O método verificará se há clientes conectados e, se houver, o timer timerUpdateFrameBuffer será iniciado. Uma vez que o cronômetro é iniciado, o ouvinte de ação realmente fará a captura de tela e sendFrameBufferUpdate() será executado.

A figura acima mostra a relação do ouvinte com o procedimento de atualização do buffer de quadro. A maioria dos ouvintes são acionados quando o usuário faz uma ação: clica, seleciona texto, digita algo na área de texto, etc. Em seguida, a função membro doIncrementalFramebufferUpdate() é executada, o que inicia o timer timerUpdateFrameBuffer . O temporizador eventualmente chamará o método sendFrameBufferUpdate() na classe RFBService e causará atualização de tela no lado do cliente (visualizador VNC).

Capturar tela, tocar teclas e mover o ponteiro do mouse na tela

Java possui uma classe Robot integrada que permite ao desenvolvedor escrever um aplicativo que capturará capturas de tela, enviará chaves, manipulará o ponteiro do mouse, produzirá cliques, etc.

Para pegar a área da tela onde a janela JFrame é exibida, RobotScreen é usado. O método principal é getScreenshot(int, int, int, int) que captura uma região da tela. Os valores RGB para cada pixel são armazenados em uma matriz 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; }

O método armazena pixels no array colorImageBuffer. Para obter dados de pixel, o método getColorImageBuffer() pode ser usado.

O método também salva o buffer de imagem anterior. É possível obter apenas pixels que foram alterados. Para obter apenas a diferença da área da imagem, use o método getDeltaImageBuffer() .

Enviar pressionamentos de tecla para o sistema é fácil com a classe Robot. No entanto, alguns códigos de chave especiais recebidos de visualizadores VNC devem ser traduzidos corretamente primeiro. A classe RobotKeyboard possui o método sendKey(int, int) que trata de chaves especiais e alfanuméricas:

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

O estado do argumento determina se a tecla é pressionada ou liberada. Após a tradução correta do código da chave para a constante VT, o método doType(int, int) passa o valor da chave para o Robot e o efeito é o mesmo que o usuário local apertou a tecla no teclado:

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

Semelhante ao RobotKeyboard é a classe RobotMouse que manipula eventos de ponteiro e faz com que o ponteiro do mouse se mova e clique.

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

Todas as três classes RobotScreen, RobotMouse e RobotKeyboard alocam uma nova instância Robot no construtor:

 this.robot = new Robot();

Temos apenas uma instância de cada, pois não há necessidade no nível do aplicativo de ter mais de uma instância da 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(); ... }

Neste aplicativo de demonstração, essas instâncias são criadas na função main() .

O resultado é um aplicativo baseado em Swing em Java que atua como um provedor de serviços RFB e permite que visualizadores VNC padrão se conectem a ele:

Conclusão

O protocolo RFB é amplamente utilizado e aceito. Existem implementações de cliente na forma de visualizadores VNC para quase todas as plataformas e dispositivos. O objetivo principal é exibir desktops remotamente, mas também pode haver outros aplicativos. Por exemplo, você pode criar ferramentas gráficas bacanas e acessá-las remotamente para aprimorar seus fluxos de trabalho remotos existentes.

Este artigo aborda os fundamentos do protocolo RFB, formato de mensagem, como enviar parte da tela e como lidar com teclado e mouse. O código-fonte completo com o aplicativo de demonstração Swing está disponível no GitHub.