Robot faz “Ctrl+C” mas Clipboard demora para receber o Conteúdo Copiado

Estou trabalhando com um Robot (java.awt.Robot) que faz o Atalho de Teclado “Ctrl+C” e com um Clipboard (java.awt.datatransfer.Clipboard) para pegar da Área de Transferência o texto copiado pelo Robot.

Entretanto, após o Robot fazer o Ctrl+C leva algum tempo até o texto copiado realmente ir parar na Área de Transferência, e isto faz com que eu não consiga (por muitas vezes) capturar o texto Copiado pelo Robot porque ele ainda não foi colocado na Área de Transferência, de modo que estou usando um Thread.sleep(long ms) para aguardar o texto copiado pelo Robot chegar na Área de Transferência. Mas, usar Thread.sleep(long ms) para esta tarefa tem vários problemas que vou explicar no final.

Criei um código de exemplo que ilustra bem o problema, vou apresentá-lo por partes:


Método que usa o Clipboard para ler o texto atualmente na Área de Transferência:

private static Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); //variável estática com o Clipboard
	
	private static String capturarTextoDaAreaDeTransferencia() throws UnsupportedFlavorException, IOException {
		try {
			if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor))
				return clipboard.getData(DataFlavor.stringFlavor).toString();//Retorna o texto na Área de Transferência.
			else
				return "";//Não há texto na Área de Transferência, então Retorna uma String vazia.
		} catch (IllegalStateException e) {
			System.out.println("\n> A Área de Transferência está indisponível neste instante: "+e);
			sleep(100); //"Thread.sleep(100)": aguardamos 100ms para depois tentar ler a Clipboard novamente.
			return capturarTextoDaAreaDeTransferencia(); //Recursividade: Tentamos ler a Clipboard novamente, até conseguir.
		}
	}

Observe que este método possui um try/catch para pegar a Exception IllegalStateException, esta Exception tem sido lançada às vezes, e acredito que ela ocorra quando a Área de Transferência ainda não foi liberada após o Robot fazer o Ctrl+C. Quando esta Exception ocorre, são aguardados 100ms (aguardamos a Área de Transferência ficar disponível) e o método chama a si próprio recursivamente para tentar de novo. O método fica num loop recursivo até conseguir acessar a Área de Transferência, esta solução não parece elegante mas não é meu real problema.


O método sleep(long millissegundos), que simplesmente bloqueia a Thread atual:

private static void sleep(long millissegundos) {
	try {
		Thread.sleep(millissegundos);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

O método main, onde o Robot faz o Ctrl+C e lemos a Área de Transferência, é onde o problema aparece:

public static void main(String[] args) throws AWTException, UnsupportedFlavorException, IOException {

		System.out.println("> Texto no ClipBoard antes da execução:\n"+capturarTextoDaAreaDeTransferencia());
		
		Robot robot = new Robot();
		robot.keyPress(KeyEvent.VK_CONTROL);
        robot.keyPress(KeyEvent.VK_C);
        robot.keyRelease(KeyEvent.VK_C);
        robot.keyRelease(KeyEvent.VK_CONTROL);
        
        System.out.println("\n> Texto no ClipBoard imediatamente após o 'Ctrl+C':\n"+capturarTextoDaAreaDeTransferencia());
        
        sleep(500); //"Thread.sleep(500)", para esta Thread ficar parada aqui por 500ms. 
        
        System.out.println("\n> Texto no ClipBoard 500ms depois do 'Ctrl+C':\n"+capturarTextoDaAreaDeTransferencia());
	}

Para testar esse código, você precisa fazer isto ANTES de executá-lo:

  1. Copiar algum texto para sua Área de Transferência fazendo Ctrl+C manualmente, digamos que você copie o texto “Conteúdo na Clipboard antes da Execução”;
  2. Selecionar OUTRO Texto (eu seleciono um Texto qualquer na própria IDE), que será o Texto que o Robot irá copiar (ao fazer Ctrl+C) quando a execução for iniciada. Você precisa garantir que a Janela com o Texto Selecionado estará em Foco quando a execução for iniciada para que o Ctrl+C do Robot consiga copiar este Texto para a Área de Transferência, e a forma mais fácil de fazer isso é selecionar um código qualquer na IDE e iniciar a Execução (Run). Digamos que você selecione o texto “Conteúdo Selecionado antes da Execução”.

Saída da Execução (varia a cada Execução):

No meu PC a Saída é geralmente assim:

Texto no ClipBoard antes da execução:
Conteúdo na Clipboard antes da Execução

Texto no ClipBoard imediatamente após o ‘Ctrl+C’:
Conteúdo na Clipboard antes da Execução

Texto no ClipBoard 500ms depois do ‘Ctrl+C’:
Conteúdo Selecionado antes da Execução

Observe que o Texto capturado da Área de Transferência imediatamente após o Robot fazer o Ctr+C ainda é o mesmo Texto que estava na Área de Transferência antes da Execução ser iniciada; entretanto, após aguardarmos os 500ms e lermos a Área de Transferência novamente, obtemos o Texto Selecionado antes da Execução que durante a Execução o Robot copiou para a Área de Transferência.
Isto prova que leva certo tempo até o texto copiado pelo Robot ser colocado na Área de Transferência, e, se lermos a Área de Transferência antes deste tempo iremos obter o que já estava na Área de Transferência antes da Execução ao invés de obtermos o Texto Selecionado sobre o qual o Robot fez o Ctrl+C.

De vez em quando, a Saída é como deveria ser:

Texto no ClipBoard antes da execução:
Conteúdo na Clipboard antes da Execução

Texto no ClipBoard imediatamente após o ‘Ctrl+C’:
Conteúdo Selecionado antes da Execução

Texto no ClipBoard 500ms depois do ‘Ctrl+C’:
Conteúdo Selecionado antes da Execução

Isto ocorre por mero acaso, é uma questão de Concorrência entre Threads, ou seja, é instável.


Porque Thread.sleep(long ms) não é uma boa solução:

Como eu disse, estou usando um Thread.sleep(long ms) de 500ms para aguardar até que o Texto Selecionado antes da Execução e copiado pelo Robot durante a Execução chegue na Área de Transferência, e então eu possa capturá-lo sem correr o risco de capturar o Texto que estava na Área de Transferência antes mesmo da Execução iniciar.

Entretanto, a verdade é que o risco ainda existe, pois se o PC ficar lento ou o programa for executado em um PC ou Sistema Operacional que gaste mais tempo para atualizar a Área de Transferência, os 500ms de aguardo podem não ser suficientes.

Além disso, colocar um Thread.sleep(500) no programa gera um atraso indesejado, eu gostaria de diminuir o máximo possível o tempo deste sleep, mas, quanto menor ele for, maior é a chance do problema ocorrer.


Como Solucionar?

Acredito que a melhor solução para este problema seria ter algum meio de pausar a execução até que o Texto Selecionado antes da execução e copiado pelo Robot durante a execução fosse colocado na Área de Transferência, assim eu não precisaria colocar um sleep de valor arbitrário para aguardar a Área de Transferência ser atualizar com o que foi Copiado pelo Robot, pois, a execução da Aplicação só seguiria adiante após a Área de Transferência ter sido atualizada.

Algo assim:

public static void main(String[] args) throws AWTException, UnsupportedFlavorException, IOException {

		System.out.println("> Texto no ClipBoard antes da execução:\n"+capturarTextoDaAreaDeTransferencia());
		
		Robot robot = new Robot();
		robot.keyPress(KeyEvent.VK_CONTROL);
        robot.keyPress(KeyEvent.VK_C);
        robot.keyRelease(KeyEvent.VK_C);
        robot.keyRelease(KeyEvent.VK_CONTROL);
        
        aguardarAAtualizacaoDaAreaDeTransferencia(); //Como aguardar a Área de Transferência ser atualizada após o Ctrl+C do Robot?
        
        System.out.println("\n> Texto depois do 'Ctrl+C':\n"+capturarTextoDaAreaDeTransferencia());
	}

E a Saída deveria ser sempre a mesma ao invés de variar de Execução para Execução, sendo algo assim:

Texto no ClipBoard antes da execução:
Conteúdo na Clipboard antes da Execução

Texto depois do ‘Ctrl+C’:
Conteúdo Selecionado antes da Execução


Código Completo para que você possa Testá-lo:

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.KeyEvent;
import java.io.IOException;

public class TesteComRobotEClipboard {
	
	private static Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
	
	private static String capturarTextoDaAreaDeTransferencia() throws UnsupportedFlavorException, IOException {
		try {
			if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor))
				return clipboard.getData(DataFlavor.stringFlavor).toString();//Retorna o texto na Área de Transferência.
			else
				return "";//Não há texto na Área de Transferência, então Retorna uma String vazia.
		} catch (IllegalStateException e) {
			System.out.println("\n> A Área de Transferência está indisponível neste instante: "+e);
			sleep(100); //"Thread.sleep(100)": aguardamos 100ms para depois tentar ler a Clipboard novamente.
			return capturarTextoDaAreaDeTransferencia(); //Recursividade: Tentamos ler a Clipboard novamente, até conseguir.
		}
	}
	
	private static void sleep(long millissegundos) {
		try {
			Thread.sleep(millissegundos);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws AWTException, UnsupportedFlavorException, IOException {

		System.out.println("> Texto no ClipBoard antes da execução:\n"+capturarTextoDaAreaDeTransferencia());
		
		Robot robot = new Robot();
		robot.setAutoWaitForIdle(true);
		robot.keyPress(KeyEvent.VK_CONTROL);
        robot.keyPress(KeyEvent.VK_C);
        robot.keyRelease(KeyEvent.VK_C);
        robot.keyRelease(KeyEvent.VK_CONTROL);
        
        System.out.println("\n> Texto no ClipBoard imediatamente após o 'Ctrl+C':\n"+capturarTextoDaAreaDeTransferencia());
        
        sleep(500); //"Thread.sleep(500)", para esta Thread ficar parada aqui por 500ms. 
        
        System.out.println("\n> Texto no ClipBoard 500ms depois do 'Ctrl+C':\n"+capturarTextoDaAreaDeTransferencia());
	}

}

Amigo, você resolveu seu problema? estou na mesma situação…se puder me passar seu contato