Como obter janela/processo de primeiro plano em Java (usando JNA) no MacOS?
Atualmente, estou trabalhando para obter a janela/processo de primeiro plano (superior) no MS Windows. Preciso fazer algo semelhante no macOS usando JNA.
Qual é o código equivalente no macOS?
byte[] windowText = new byte[512];
PointerType hwnd = User32.INSTANCE.GetForegroundWindow();
User32.INSTANCE.GetWindowTextA(hwnd, windowText, 512);
System.out.println(Native.toString(windowText));
Respostas
Na verdade, existem duas questões aqui, janela de primeiro plano e processo de primeiro plano. Vou tentar responder a ambos.
Para o processo de primeiro plano, uma maneira fácil de usar o JNA é mapear a API do Application Services . Observe que essas funções foram introduzidas na versão 10.9 e agora estão obsoletas, mas ainda funcionam a partir da versão 10.15. A versão mais recente está no AppKitLibrary, veja abaixo.
Crie esta classe, mapeando as duas funções que você vai precisar:
public interface ApplicationServices extends Library {
ApplicationServices INSTANCE = Native.load("ApplicationServices", ApplicationServices.class);
int GetFrontProcess(LongByReference processSerialNumber);
int GetProcessPID(LongByReference processSerialNumber, IntByReference pid);
}
O processo de "primeiro plano" pode ser obtido com GetFrontProcess(). Isso retorna algo chamado ProcessSerialNumber, um valor exclusivo de 64 bits usado em toda a API do Application Services. Para traduzi-lo para o uso do espaço do usuário, você provavelmente deseja o ID do processo e GetProcessPID()faz essa tradução para você.
LongByReference psn = new LongByReference();
IntByReference pid = new IntByReference();
ApplicationServices.INSTANCE.GetFrontProcess(psn);
ApplicationServices.INSTANCE.GetProcessPID(psn, pid);
System.out.println("Front process pid: " + pid.getValue());
Enquanto o acima funciona, é obsoleto. Um novo aplicativo deve usar AppKitLibrary:
public interface AppKitLibrary extends Library {
AppKitLibrary INSTANCE = Native.load("AppKitLibrary", AppKitLibrary.class);
}
Existem várias outras perguntas do StackOverflow sobre o aplicativo mais alto que usa essa biblioteca, como esta . Mapear todas as importações e objetos necessários é muito mais trabalhoso do que tenho tempo para responder aqui, mas você pode achar útil. Provavelmente é mais fácil descobrir como usar a estrutura Rococoa (que usa JNA sob o capô, mas já mapeou todo o AppKit via JNAerator) para acessar essa API. Alguns javadocs estão aqui .
Também existem soluçõesAppleScript que podem ser executadas a partir do Java via linha de comando usando e Runtime.exec()capturando a saída.
Com relação à janela de primeiro plano na tela, é um pouco mais complicado. Em minha resposta à sua pergunta anterior sobre a iteração de todas as janelas no macOS, respondi como obter uma lista de todas as janelas usando CoreGraphicsJNA, incluindo uma CFDictionarycontendo mais informações.
Uma dessas chaves do dicionário é kCGWindowLayera que retornará um CFNumberrepresentando o número da camada da janela. Os documentos afirmam que isso é de 32 bits, portanto, intValue()é apropriado. O número é a "ordem de desenho", portanto, um número maior substituirá um número menor. Assim, você pode iterar todas as janelas recuperadas e encontrar o número máximo. Esta será a camada de "primeiro plano".
Existem algumas ressalvas:
- Na verdade, existem apenas 20 camadas disponíveis. Muitas coisas compartilham uma camada.
- A camada 1000 é o protetor de tela. Você pode ignorar as camadas 1000 e superiores.
- A Camada 24 é o Dock, geralmente na parte superior, com a Camada 25 (os ícones no encaixe) em um nível superior.
- A camada 0 parece ser o restante da área de trabalho.
- Qual janela está "no topo" depende de onde você olha na tela. Sobre o dock, o dock ficará em primeiro plano (ou o ícone do aplicativo). No restante da tela, você precisa verificar o pixel que está avaliando em relação ao retângulo da tela obtido na janela do CoreGraphics. (Use a
kCGWindowBoundstecla que retorna aCGRect(uma estrutura com 4 duplos, X, Y, largura, altura).
Você precisará filtrar para janelas na tela. Se você já obteve a lista, pode usar a kCGWindowIsOnscreenchave para determinar se a janela está visível. Ele retorna um CFBoolean. Como essa chave é opcional, você precisará testar null. No entanto, se você estiver começando do nada, seria melhor usar a kCGWindowListOptionOnScreenOnly Constante de opção de janela ao chamar inicialmente CGWindowListCopyWindowInfo().
Além de iterar todas as janelas, a CGWindowListCopyWindowInfo()função recebe um CGWindowIDparâmetro relativeToWindowe você pode adicionar (com ou bit a bit) kCGWindowListOptionOnScreenAboveWindowàs opções.
Por fim, você pode descobrir que limitar as janelas associadas à sessão atual pode ser útil e mapear CGWindowListCreate()usando uma sintaxe semelhante à CopyInfo()variante. Ele retorna uma matriz de números de janela à qual você pode limitar sua pesquisa no dicionário ou passar essa matriz como um argumento para CGWindowListCreateDescriptionFromArray().
Conforme mencionado em minha resposta anterior, você "possui" todos os objetos que cria usando Createou Copyfunções e é responsável por liberá-los quando terminar de usá-los, para evitar vazamentos de memória.
AppleScriptEngine appleEngine = new apple.applescript.AppleScriptEngine();
ArrayList<String> processNames = null;
try {
String processName = null;
processNames = (ArrayList<String>) appleEngine
.eval("tell application \"System Events\" to get name of application processes whose frontmost is true and visible is true");
if (processNames.size() > 0) {
processName = processNames.get(0);// the front most process name
}
return processName;
} catch (ScriptException e) {
log.debug("no app running");
}