我想知道如何编写一个Java程序,可以知道哪个Windows应用程序处于焦点状态。我可能打开了很多窗口,但我想知道正在使用的窗口(就像我现在输入这些文字时使用的Google Chrome窗口一样)。
我不需要更改窗口或应用程序中的任何内容,只需要知道它的名称。
正如其他人已经指出的,没有一种可移植的方式可以在所有平台上获取这个信息。但更糟糕的是:在MS Windows上甚至没有一种一致的方式。我将提供一些代码,以解决不同平台上的问题,并指出其限制。请自行承担使用风险,由于安全原因,该代码可能会提供错误的结果或根本无法运行。如果它在您的机器上运行,这并不意味着它在其他机器上也能同样良好地运行。
代码使用JNA。在我的实验中,我遇到了不同版本的JNA和JNA平台库的问题。最好自己编译它,这样就可以获得一致的环境。
kichik所提供的答案在当时是正确的,但在某些情况下在Windows 8上无法正常工作。问题在于它无法正确处理Metro应用程序。不幸的是,目前还没有稳定的API来获取当前正在运行的Metro应用程序的名称。我已经在代码中插入了一些提示,但最好等待微软提供API。
在Windows上,您还会遇到特权应用程序和UAC对话框的问题。因此,您将不总是能够获得正确的答案。
public interface Psapi extends StdCallLibrary {
Psapi INSTANCE = (Psapi) Native.loadLibrary("Psapi", Psapi.class);
WinDef.DWORD GetModuleBaseNameW(Pointer hProcess, Pointer hModule, byte[] lpBaseName, int nSize);
}
if (Platform.isWindows()) {
final int PROCESS_VM_READ=0x0010;
final int PROCESS_QUERY_INFORMATION=0x0400;
final User32 user32 = User32.INSTANCE;
final Kernel32 kernel32=Kernel32.INSTANCE;
final Psapi psapi = Psapi.INSTANCE;
WinDef.HWND windowHandle=user32.GetForegroundWindow();
IntByReference pid= new IntByReference();
user32.GetWindowThreadProcessId(windowHandle, pid);
WinNT.HANDLE processHandle=kernel32.OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, true, pid.getValue());
byte[] filename = new byte[512];
Psapi.INSTANCE.GetModuleBaseNameW(processHandle.getPointer(), Pointer.NULL, filename, filename.length);
String name=new String(filename);
System.out.println(name);
if (name.endsWith("wwahost.exe")) { // Metro App
// There is no stable API to get the current Metro app
// But you can guestimate the name form the current directory of the process
// To query this, see:
// https://dev59.com/PGQo5IYBdhLWcg3wXeb5
}
使用 X11 存在三个问题:
在 X11 上,查询当前拥有焦点的窗口更为合理。
public interface XLib extends StdCallLibrary {
XLib INSTANCE = (XLib) Native.loadLibrary("XLib", Psapi.class);
int XGetInputFocus(X11.Display display, X11.Window focus_return, Pointer revert_to_return);
}
if(Platform.isLinux()) { // Possibly most of the Unix systems will work here too, e.g. FreeBSD
final X11 x11 = X11.INSTANCE;
final XLib xlib= XLib.INSTANCE;
X11.Display display = x11.XOpenDisplay(null);
X11.Window window=new X11.Window();
xlib.XGetInputFocus(display, window,Pointer.NULL);
X11.XTextProperty name=new X11.XTextProperty();
x11.XGetWMName(display, window, name);
System.out.println(name.toString());
}
Mac OS X不关注窗口,而是关注应用程序。因此,询问当前活动的应用程序是有意义的。旧版本的Mac OS X提供多个桌面。新版本可以同时打开多个全屏应用程序。因此,您可能无法始终获得正确的答案。
if(Platform.isMac()) {
final String script="tell application \"System Events\"\n" +
"\tname of application processes whose frontmost is tru\n" +
"end";
ScriptEngine appleScript=new ScriptEngineManager().getEngineByName("AppleScript");
String result=(String)appleScript.eval(script);
System.out.println(result);
}
当我尝试使用这段代码时,在最基本的情况下它是可行的。但是如果你想让这段代码运行得更加可靠,那么你需要添加很多细节处理。你需要自己决定是否值得这样做。
为了让代码完整,这里是我所使用的导入部分:
import com.sun.jna.Native;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import com.sun.jna.platform.unix.X11;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
当然,您必须重新排列代码的各个部分。我使用了一个大类,其中接口在开头,然后其余部分都在一个大的主方法中。
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
获取进程的句柄Java中获取活动窗口信息的完整示例可在此处找到。
C代码可在这里找到。