Узнайте, какое приложение (окно) находится в фокусе на Java

Я хотел бы знать, как я могу закодировать программу Java, которая знает, какое приложение Windows находится в фокусе. Я могу открыть много окон, но хочу знать тот, который используется (например, Google Chrome сейчас, когда я набираю это).

Мне не нужно ничего менять в окне или приложении, просто нужно знать его имя.

Ответ 1

Я боюсь, что для этого нет java api. JVM ничего не знает о тех окнах, которыми он не управляется. Вероятно, вам придется использовать JNI и вызвать эту функцию

[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();

Ссылка MSDN

PS. Это функция GetWindowText, которую вы можете использовать, если вам нужно захватить заголовок окна.

В этом сообщении есть примеры JNI, которые могут вам помочь.

Ответ 2

Поскольку другие уже имеют указатель, нет никакого переносного способа получить это на всех платформах. Но ухудшить ситуацию: в MS Windows даже не существует последовательного способа. Я предоставлю некоторый код, который решит проблему для разных платформ и укажет на ограничения. Используйте на свой страх и риск, код может давать неправильные результаты или вообще не запускаться из-за соображений безопасности. Если он работает на вашем компьютере, это не означает, что он будет работать одинаково хорошо на других машинах.

В коде используется JNA. Во время моих экспериментов у меня были проблемы с различными версиями JNA и библиотеки платформы JNA. Лучше всего скомпилировать его, поэтому у вас есть согласованная среда.

Окна

Ответ, предоставленный kichik, был правильным в свое время, но не будет работать с Windows 8 во всех случаях. Проблема в том, что он не будет корректно обрабатывать приложения Metro. К сожалению, в настоящее время нет стабильного API для получения имени текущего приложения Metro. Я добавил некоторые подсказки в код, но лучше подождать, пока Microsoft не предоставит вам 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:
            // http://stackoverflow.com/questions/16110936/read-other-process-current-directory-in-c-sharp
        }

Linux/Unix/X11

С X11 мы имеем три проблемы:

  • Из-за прозрачности сети в одном и том же X11 могут быть смешаны несколько окон с совершенно разных машин. Поэтому ни имя, ни PID процесса, принадлежащие окну, могут иметь смысл на машине, которую вы запрашиваете.
  • Большинство менеджеров окон имеют несколько рабочих столов. На каждом рабочем столе на переднем плане может быть другое приложение
  • Менеджеры оконной панели (например, XMonad) не имеют концепции окна переднего плана. Они упорядочивают все окна в некотором смысле, поэтому каждое окно находится на переднем плане в то же время.

В 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 не фокусируется на окнах, а на приложениях. Поэтому имеет смысл запросить активное приложение. В старых версиях 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;

Конечно, вам придется перестроить части кода. Я использовал один большой класс с интерфейсами в начале a, а затем остальные в одном большом главном методе.

Ответ 3

Как сказал Hovercraft Full Of Eels, JNA - ваш лучший выбор здесь. В отличие от JNI, вам не придется компилировать для него код C.

Чтобы получить имя процесса:

  • Вызовите GetForegroundWindow(), чтобы получить дескриптор окна
  • Вызовите GetWindowThreadProcessId(), чтобы выяснить, какой процесс принадлежит ему.
  • Вызовите OpenProcess(), чтобы получить дескриптор процесса (с PROCESS_QUERY_INFORMATION | PROCESS_VM_READ)
  • Вызовите GetModuleFileNameEx(), чтобы получить имя процесса из дескриптора. Вы также можете вызвать GetModuleBaseName() только для имени модуля без полного пути.

Полный пример доступен в Получение активной информации о окне в Java

C код можно найти здесь.