Using JNA to get the active program on Windows


This question on StackOverflow explains how to get the currently active program on Windows. This means, the program that is in the foreground, and receiving user input. However, there’s a lot going on there that isn’t explained. And the code could use a bit of cleaning up.

The example uses JNA , or Java Native Access. JNA is a way of accessing platform dependent functions, without the development overhead that JNI (Java Native Interface) requires. You’ll need to read through the tutorial to really get going with JNA, since it’s not that easy.

Library mapping

The first thing you need to do to access the native functions, is to map library you want to use. You can do this using an interface, or using a class. In this case, we’ll use a class. The following code will load the Process Status API.

static class Psapi
 {
     static
     {
         Native.register("psapi");
     }
 }

Function mapping

When the library is mapped, you need to map the functions of that particular library you want to use. Depending on the method you chose for mapping the library, this too can be done in an interface or a class. Since we chose the class, the method will be added there.

static class Psapi
{
     //mapping the library is skipped.
     public static native int GetModuleBaseNameW(Pointer hProcess, Pointer hmodule, char[] lpBaseName, int size);
}

Getting the needed functions

The example displays two properties of the active window: the title, and the name of the corresponding process. To get this information, we need the following libraries and functions:

psapi:
GetModuleBaseNameW

user32:
GetWindowThreadProcessId
GetForegroundWindow
GetWindowTextW 

kernel32:
OpenProcess 

In addition, we need some static fields to correctly call kernel32’s OpenProcess:

 public static int PROCESS_QUERY_INFORMATION = 0x0400; //1
 public static int PROCESS_VM_READ = 0x0010; //1

Getting the title of the active window

To get the title of the active window, we need to do the following:

  • Create a buffer
  • Get the active window
  • Read the title to the buffer
  • Convert the contents of the buffer to a String

In code, it looks like this:

private static String getActiveWindowTitle(){
     char[] buffer = new char[MAX_TITLE_LENGTH * 2]; // create buffer
     HWND foregroundWindow = User32DLL.GetForegroundWindow(); // get active window
     User32DLL.GetWindowTextW(foregroundWindow, buffer, MAX_TITLE_LENGTH); // read title into buffer
     String title =  Native.toString(buffer); // convert buffer to String
     return title;
}

Getting the active window process name

Getting the name of the process is a bit more complicated. To do this, we need the following steps:

  • Create a buffer
  • Create a pointer
  • Get the active window
  • Get a reference to the process ID
  • Get a reference to the process
  • Read the name of the process to the buffer
  • Convert the contents of the buffer to a String

In code, it looks like this:

private static String getActiveWindowProcess(){
     char[] buffer = new char[MAX_TITLE_LENGTH * 2]; // create buffer
     PointerByReference pointer = new PointerByReference(); // create pointer
     HWND foregroundWindow = User32DLL.GetForegroundWindow(); // get active window
     User32DLL.GetWindowThreadProcessId(foregroundWindow, pointer); // Get a reference to the process ID
     Pointer process = Kernel32.OpenProcess(Kernel32.PROCESS_QUERY_INFORMATION | Kernel32.PROCESS_VM_READ, false, pointer.getValue()); // get a reference to the process
     Psapi.GetModuleBaseNameW(process, null, buffer, MAX_TITLE_LENGTH); // read the name of the process into buffer
     String processName = Native.toString(buffer); // convert buffer to String
     return processName;
}

Full code

The complete program will check every second which window is in the foreground, and reports any changes. It will also display how long the window was in the foreground.

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.ptr.PointerByReference;

public class EnumerateWindows 
{
     private static final int MAX_TITLE_LENGTH = 1024;

     public static void main(String[] args) throws Exception
     {
          String lastTitle = "none";
          String lastProcess = "none";
          long lastChange = System.currentTimeMillis();

          while (true)
          {
               String currentTitle = getActiveWindowTitle();
               String currentProcess = getActiveWindowProcess();
               if (!lastTitle.equals(currentTitle))
               {
                    long change = System.currentTimeMillis();
                    long time = (change - lastChange) / 1000;
                    lastChange = change;
                    System.out.println("Change! Last title: " + lastTitle + " lastProcess: " + lastProcess + " time: " + time + " seconds");
                    lastTitle = currentTitle;
                    lastProcess = currentProcess;
               }
               try
               {
                    Thread.sleep(1000);
               }
               catch (InterruptedException ex)
               {
                    // ignore
               }
          }
     }

     private static String getActiveWindowTitle()
     {
          char[] buffer = new char[MAX_TITLE_LENGTH * 2];
          HWND foregroundWindow = User32DLL.GetForegroundWindow();
          User32DLL.GetWindowTextW(foregroundWindow, buffer, MAX_TITLE_LENGTH);
          String title = Native.toString(buffer);
          return title;
     }

     private static String getActiveWindowProcess()
     {
          char[] buffer = new char[MAX_TITLE_LENGTH * 2];
          PointerByReference pointer = new PointerByReference();
          HWND foregroundWindow = User32DLL.GetForegroundWindow();
          User32DLL.GetWindowThreadProcessId(foregroundWindow, pointer);
          Pointer process = Kernel32.OpenProcess(Kernel32.PROCESS_QUERY_INFORMATION | Kernel32.PROCESS_VM_READ, false, pointer.getValue());
          Psapi.GetModuleBaseNameW(process, null, buffer, MAX_TITLE_LENGTH);
          String processName = Native.toString(buffer);
          return processName;
     }

     static class Psapi
     {
          static
          {
               Native.register("psapi");
          }

          public static native int GetModuleBaseNameW(Pointer hProcess, Pointer hmodule, char[] lpBaseName, int size);
   }

     static class Kernel32
     {
          static
          {
               Native.register("kernel32");
          }

          public static int PROCESS_QUERY_INFORMATION = 0x0400;
          public static int PROCESS_VM_READ = 0x0010;

          public static native Pointer OpenProcess(int dwDesiredAccess, boolean bInheritHandle, Pointer pointer);
     }

     static class User32DLL
     {
          static
          {
               Native.register("user32");
          }

          public static native int GetWindowThreadProcessId(HWND hWnd, PointerByReference pref);
          public static native HWND GetForegroundWindow();
          public static native int GetWindowTextW(HWND hWnd, char[] lpString, int nMaxCount);
     }
}

6 reacties op “Using JNA to get the active program on Windows”

  1. Dear Ghyze,

    Thanks for the excellent post. I am a newbie to working with DLLs. I would like to know why we need to register User32.dll and Kernel32.dll? Can this registration process be skipped?
    I also came to know that using JNA 4.0+ we can skip the registration of DLLs part.
    Please share your views.

    Thanks,
    Prazy

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *