最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to get the currently selected application from Windows in Java - Stack Overflow

programmeradmin0浏览0评论

I am trying to find out which of the applications that are currently running is the selected one.

For example, if the open applications are Chrome, Spotify, File Manager and the Settings. The user is in the Settings app, so that is the currently selected application.

I want to get the name or the PID or anything that identifies this application in Java.

I am trying to find out which of the applications that are currently running is the selected one.

For example, if the open applications are Chrome, Spotify, File Manager and the Settings. The user is in the Settings app, so that is the currently selected application.

I want to get the name or the PID or anything that identifies this application in Java.

Share Improve this question edited Feb 3 at 9:26 Mark Rotteveel 110k229 gold badges156 silver badges225 bronze badges asked Feb 2 at 22:30 MineRickStarMineRickStar 439 bronze badges 7
  • 1 Your problem is that you've chosen a particularly bad language in which to do this. Java is really not good at interacting with a native operating system. You need a native language to do that. C# would be a great deal better – g00se Commented Feb 2 at 22:48
  • I am quite comftable with java, but I know it's not the best. But there has to be a way – MineRickStar Commented Feb 2 at 22:58
  • Does it have to be in pure Java? If not, could you do this in C and Java Native Interface? – Old Dog Programmer Commented Feb 3 at 1:09
  • If I have to I maybe could, but I have never done that before, and I don't know much C either. – MineRickStar Commented Feb 3 at 1:23
  • 1 GetActiveWindow is for the calling thread, use GetForegroundWindow. – DuncG Commented Feb 3 at 12:15
 |  Show 2 more comments

3 Answers 3

Reset to default 2

If you use JDK16+ it is quite easy to read the PID of current foreground window in pure Java code, making use of Foreign Function Memory API. It is best to use JDK22 onwards as FFM was incubating or preview before that release. You will need to call these Windows API methods:

  • GetForegroundWindow to find the current foreground window
  • GetWindowTextW optional to read the label - useful for debugging
  • GetWindowThreadProcessId find the PID

This demo just loops so you can test out by selecting different windows as it runs, refactor as needed:

// Needs import java.lang.foreign.*; java.lang.invoke.MethodHandle 

final int maxChars = 1024;
try(Arena arena = Arena.ofConfined()) {

    // Allocations for GetWindowTextW + GetWindowThreadProcessId
    MemorySegment lpString = arena.allocate(JAVA_CHAR, maxChars);
    MemorySegment lpdwProcessId = arena.allocate(Native_h.DWORD);

    for (int i = 0; i < 100; i++) {
        if (i > 0) Thread.sleep(1000L);

        MemorySegment hWnd = Native_h.GetForegroundWindow();
        int used = Native_h.GetWindowTextW(hWnd, lpString, maxChars-1);
        Objects.checkIndex(used, maxChars);
        String text = lpString.getString(0, StandardCharsets.UTF_16LE);

        int rc = Native_h.GetWindowThreadProcessId(hWnd, lpdwProcessId);
        // NB check that (rc != 0)
        int pid = lpdwProcessId.get(Native_h.DWORD, 0);
        System.out.format("GetForegroundWindow  hWnd:%x => `%s` PID %d%n", hWnd.address(), text, pid);
    }
}

The code above is very simple. But there is one non-trivial problem - what is Native_h? Fortunately you can use jextract to build Java code for any Windows header definitions needed for these methods. If you don't have the headers, get Microsoft Visual Studio.

echo #include ^<shlobj_core.h^> > Native.h

jextract -luser32 -t yourpackage.name --output src --include-function GetForegroundWindow --include-function GetWindowTextW --include-function GetWindowThreadProcessId Native.h

To simplify, here is a version which provides same functionality as jextract which handles the resolution of those native Windows methods:

class Native_h {
    static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("user32"), Arena.ofAuto());

    public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char");
    public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int");
    public static final ValueLayout.OfInt C_LONG = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("long");
    public static final OfInt DWORD = C_LONG;
    public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*"))
            .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR));

    public static final MethodHandle GetForegroundWindow$HANDLE = Linker.nativeLinker().downcallHandle(SYMBOL_LOOKUP.find("GetForegroundWindow").get(), FunctionDescriptor.of(Native_h.C_POINTER));
    public static MemorySegment GetForegroundWindow() {
        var mh$ = GetForegroundWindow$HANDLE;
        try {
            return (MemorySegment)mh$.invokeExact();
        } catch (Throwable ex$) {
           throw new AssertionError("should not reach here", ex$);
        }
    }
    public static final MethodHandle GetWindowTextW$HANDLE = Linker.nativeLinker().downcallHandle(
            SYMBOL_LOOKUP.find("GetWindowTextW").get(),
            FunctionDescriptor.of(Native_h.C_INT, Native_h.C_POINTER, Native_h.C_POINTER, Native_h.C_INT));

    public static int GetWindowTextW(MemorySegment hWnd, MemorySegment lpString, int nMaxCount) {
        var mh$ = GetWindowTextW$HANDLE;
        try {
            return (int)mh$.invokeExact(hWnd, lpString, nMaxCount);
        } catch (Throwable ex$) {
           throw new AssertionError("should not reach here", ex$);
        }
    }
    public static final MethodHandle GetWindowThreadProcessId$HANDLE = Linker.nativeLinker().downcallHandle(
            SYMBOL_LOOKUP.find("GetWindowThreadProcessId").get(), FunctionDescriptor.of(Native_h.C_LONG, Native_h.C_POINTER, Native_h.C_POINTER));
    public static int GetWindowThreadProcessId(MemorySegment hWnd, MemorySegment lpdwProcessId) {
        var mh$ = GetWindowThreadProcessId$HANDLE;
        try {
            return (int)mh$.invokeExact(hWnd, lpdwProcessId);
        } catch (Throwable ex$) {
           throw new AssertionError("should not reach here", ex$);
        }
    }
}

I wrote this shortly before DuncG posted his answer. I realize it heavily overlaps his answer. (It’s interesting that we both had very similar ideas about which Windows native functions to use.) I have decided to post this anyway, as a more compact alternative that doesn’t require any extraction or code generation.

Like him, I suggest using the native GetForegroundWindow, GetWindowThreadProcessId, and GetWindowTextW functions of Windows:

import java.nio.charset.StandardCharsets;

import java.util.Optional;

import java.lang.invoke.MethodHandle;

import java.lang.foreign.Linker;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.ValueLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.Arena;

public class WindowsForegroundWindowDetector {
    public record WindowInfo(long pid,
                             String title,
                             MemorySegment hwnd) {
        // Deliberately empty.
    }

    private final MethodHandle GetForegroundWindow;

    private final MethodHandle GetWindowThreadProcessId;

    private final MethodHandle GetWindowTextW;

    public WindowsForegroundWindowDetector() {
        Linker linker = Linker.nativeLinker();
        SymbolLookup lookup =
            SymbolLookup.libraryLookup("User32", Arena.global());

        GetForegroundWindow = linker.downcallHandle(
            lookup.findOrThrow("GetForegroundWindow"),
            FunctionDescriptor.of(
                ValueLayout.ADDRESS)); // returns HWND

        GetWindowThreadProcessId = linker.downcallHandle(
            lookup.findOrThrow("GetWindowThreadProcessId"),
            FunctionDescriptor.of(
                ValueLayout.JAVA_INT,                           // returns DWORD
                ValueLayout.ADDRESS.withName("hWnd"),           // HWND
                ValueLayout.ADDRESS.withName("lpdwProcessId")));// LPDWORD 

        GetWindowTextW = linker.downcallHandle(
            lookup.findOrThrow("GetWindowTextW"),
            FunctionDescriptor.of(
                ValueLayout.JAVA_INT,                           // returns int
                ValueLayout.ADDRESS.withName("hWnd"),           // HWND
                ValueLayout.ADDRESS.withName("lpString"),       // LPWSTR
                ValueLayout.JAVA_INT.withName("nMaxCount")));   // int
    }

    private WindowInfo toWindowInfo(MemorySegment hwnd) {
        int rawPID;
        String title;
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment lpdwProcessId =
                arena.allocateFrom(ValueLayout.JAVA_INT, 0);

            int threadID;
            try {
                threadID = (int) GetWindowThreadProcessId.invokeExact(
                    hwnd, lpdwProcessId);
            } catch (Throwable t) {
                throw new RuntimeException(
                    "GetWindowThreadProcessId failed.", t);
            }
            if (threadID == 0) {
                throw new RuntimeException("GetWindowThreadProcessId failed.");
            }
            rawPID = lpdwProcessId.get(ValueLayout.JAVA_INT, 0);

            int maxTitleLength = 1024;
            MemorySegment titleBuffer =
                arena.allocate(ValueLayout.JAVA_CHAR, maxTitleLength);

            int titleCharCount;
            try {
                titleCharCount = (int) GetWindowTextW.invokeExact(
                    hwnd, titleBuffer, maxTitleLength);
            } catch (Throwable t) {
                throw new RuntimeException("GetWindowTextW failed.", t);
            }
            if (titleCharCount == 0) {
                throw new RuntimeException("GetWindowTextW failed.");
            }
            title = titleBuffer.getString(0, StandardCharsets.UTF_16LE);
        }

        long pid = ((long) rawPID) & 0xffffffffL;
        return new WindowInfo(pid, title, hwnd);
    }

    public Optional<WindowInfo> detectForegroundWindow() {
        MemorySegment hwnd;
        try {
            hwnd = (MemorySegment) GetForegroundWindow.invokeExact();
        } catch (Throwable t) {
            throw new RuntimeException("GetForegroundWindow failed.", t);
        }

        if (hwnd.equals(MemorySegment.NULL)) {
            return Optional.empty();
        } else {
            return Optional.of(toWindowInfo(hwnd));
        }
    }

    public static void main(String[] args) {
        Optional<WindowInfo> info =
            new WindowsForegroundWindowDetector().detectForegroundWindow();

        if (info.isPresent()) {
            System.out.println(info.get());
        } else {
            System.out.println("No foreground window found.");
        }
    }
}

You cannot do that from java itself. You need additional libraries.

Java is a multi-platform language, like many others. All such languages need to make a choice: Various features OSes offer aren't universal; OSes aren't identical, after all. They do certain things in certain ways, other OSes don't do it at all and cover the underlying user need in a completely different way. So, for such non-(near-)universal features, what should a language do?

Java has chosen not to make those accessible at all. Such features are by their nature effectively untestable except by actually having a machine that runs that OS - that's just one example of the issues with providing it. Other languages choose different routes; for example, python is quite willing to provide highly OS specific libraries.

The solution - JNI

JNI, or 'Java Native Interface' lets you deploy compiled artefacts for some native platform and interact with them from your java code. Oversimplifying a bit, 'to call C code from java' (if your OS is C based.. windows more or less is). So, you need a library written in not-java that does what you want, and then you can invoke that from your java code. This isn't 'java starts another application and talks to it' (java can do that too, via ProcessBuilder for example) - this is 'the JVM runs a DLL inside its own process and translates C-like data structures to java-like data structures to let these 2 parts of the same app communicate with each other'.

So, this is possible, but it's not easy; JNI is rather unwieldy. You also need a C-written thing that implements all your calls. You can write it yourself but this means you need to, effectively, write in e.g. C++ and compile it to a DLL, and from your question I get the sense you cannot do that or don't want to.

So, you're going to have to look for projects that have done this work for you.

You're sort of in luck; JNA + JNA-platform do what you want: Here are the docs of JNA's Platform Library which can do this.

But, it's complicated to use, and JNA isn't exactly widely used or actively developed. Here's a random snippet, 11 years old, that provided getWindowTextW to get a window title: random snippet.

As the comments said, java is not a great tool to use for highly OS specific interactions. But, if you insist on smashing this particular screw in with the back of an old shoe instead of using the right tool for the toolbox, there lies your answer. Of course, now you need to know how to wield JNA which has its own significant learning curve.

发布评论

评论列表(0)

  1. 暂无评论