I'm trying to add an application-switcher to a bigger project I'm working on. It needs to work on Windows XP/Vista/7/8. I'm using Java 1.7. Below is a sample application I created to demonstrate some of the problems I'm having. I'm very new to JNA.
Thanks very much to 'Hovercraft Full of Eels' for this answer (and many others!) which forms the basis for the test application.
Here are my questions:
Image drawing - The images that I get from the window icons are being drawn in black and white. I modified the code in getImageForWindow from this answer by McDowell (Thanks!). Is there a better way to convert HICON objects to java.awt.Image? I notice there's a method called 'fromNative' in com.sun.jna.platform.win32.W32API.HICON but I can't figure out how to use it.
Getting the icons - The call I use to get an icon handle, GetClassLongW(hWnd, GCL_HICON), doesn't return icons from 64bit windows. I think I need GetClassLongPtr for that but I can't seem to access it via JNA.
Getting the correct window list, as per the Alt-tab popup - I tried to replicate what is done in this C++ answer but I couldn't manage to get the 2nd (GetAncestor, etc) and 3rd (STATE_SYSTEM_INVISIBLE) checks implemented in Java. I'm using a poor substitute which is to exclude windows where the title is blank (which ignores some legitimate windows).
Note: JNA and Platform jars are required to run this example:
package test;
import static test.WindowSwitcher.User32DLL.*;
import static test.WindowSwitcher.Gdi32DLL.*;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef.HWND;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class WindowSwitcher
{
public static void main(String args[])
{
JFrame JF = new JFrame();
JPanel JP = new JPanel(new GridLayout(0, 1));
JF.getContentPane().add(JP);
Vector<WindowInfo> V = getActiveWindows();
for (int i = 0; i < V.size(); i++)
{
final WindowInfo WI = V.elementAt(i);
JButton JB = new JButton(WI.title);
if(WI.image != null)
{
JB.setIcon(new ImageIcon(WI.image));
}
JB.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
SetForegroundWindow(WI.hWnd);
}
});
JP.add(JB);
}
JF.setSize(600,50+V.size()*64);
JF.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JF.setAlwaysOnTop(true);
JF.setFocusableWindowState(false);
JF.setVisible(true);
}
public static Vector<WindowInfo> getActiveWindows()
{
final Vector<WindowInfo> V = new Vector();
EnumWindows(new WNDENUMPROC()
{
public boolean callback(Pointer hWndPointer, Pointer userData)
{
HWND hWnd = new HWND(hWndPointer);
// Make sure the window is visible
if(IsWindowVisible(hWndPointer))
{
int GWL_EXSTYLE = -20;
long WS_EX_TOOLWINDOW = 0x00000080L;
// Make sure this is not a tool window
if((GetWindowLongW(hWndPointer, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) == 0)
{
// Get the title bar text for the window
char[] windowText = new char[512];
GetWindowTextW(hWnd, windowText, windowText.length);
String wText = Native.toString(windowText);
// Make sure the text is not null or blank
if(!(wText == null || wText.trim().equals("")))
{
// Get the icon image for the window (if available)
Image image = getImageForWindow(hWnd, wText);
// This window is a valid taskbar button, add a WindowInfo object to the return vector
V.add(new WindowInfo(wText, hWnd, image));
}
}
}
return true;
}
}, null);
return V;
}
public static Image getImageForWindow(HWND hWnd, String wText)
{
// Get an image from the icon for this window
int hicon = GetClassLongW(hWnd, GCL_HICON);
if(hicon == 0)
return null;
Pointer hIcon = new Pointer(hicon);
int width = 64;
int height = 64;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
draw(image, hIcon, DI_NORMAL);
BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
draw(mask, hIcon, DI_MASK);
applyMask(image, mask);
return image;
}
public static void draw(BufferedImage image, Pointer hIcon, int diFlags)
{
int width = image.getWidth();
int height = image.getHeight();
Pointer hdc = CreateCompatibleDC(Pointer.NULL);
Pointer bitmap = CreateCompatibleBitmap(hdc, width, height);
SelectObject(hdc, bitmap);
DrawIconEx(hdc, 0, 0, hIcon, width, height, 0, Pointer.NULL, diFlags);
for (int x = 0; x < width; x++)
{
for (int y = 0; y < width; y++)
{
int rgb = GetPixel(hdc, x, y);
image.setRGB(x, y, rgb);
}
}
DeleteObject(bitmap);
DeleteDC(hdc);
}
private static void applyMask(BufferedImage image,
BufferedImage mask)
{
int width = image.getWidth();
int height = image.getHeight();
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
int masked = mask.getRGB(x, y);
if ((masked & 0x00FFFFFF) == 0)
{
int rgb = image.getRGB(x, y);
rgb = 0xFF000000 | rgb;
image.setRGB(x, y, rgb);
}
}
}
}
static class User32DLL
{
static
{
Native.register("user32");
}
public static native int GetWindowTextW(HWND hWnd, char[] lpString, int nMaxCount);
public static native boolean EnumWindows(WNDENUMPROC lpEnumFunc, Pointer arg);
public static interface WNDENUMPROC extends com.sun.jna.win32.StdCallLibrary.StdCallCallback
{
boolean callback(Pointer hWnd, Pointer arg);
}
public static native boolean IsWindowVisible(Pointer hWnd);
public static native boolean SetForegroundWindow(HWND hWnd);
public static native int GetWindowLongW(Pointer hWnd, int nIndex);
public static int GCL_HICON = -14;
public static int GCL_HICONSM = -34;
public static native int GetClassLongW(HWND hWnd, int nIndex);
/** @see #DrawIconEx(Pointer, int, int, Pointer, int, int, int, Pointer, int) */
public static final int DI_COMPAT = 4;
public static final int DI_DEFAULTSIZE = 8;
public static final int DI_IMAGE = 2;
public static final int DI_MASK = 1;
public static final int DI_NORMAL = 3;
public static final int DI_APPBANDING = 1;
/** http://msdn.microsoft.com/en-us/library/ms648065(VS.85).aspx */
public static native boolean DrawIconEx(Pointer hdc, int xLeft,
int yTop, Pointer hIcon, int cxWidth, int cyWidth,
int istepIfAniCur, Pointer hbrFlickerFreeDraw,
int diFlags);
}
static class Gdi32DLL
{
static
{
Native.register("gdi32");
}
/** http://msdn.microsoft.com/en-us/library/dd183489(VS.85).aspx */
public static native Pointer CreateCompatibleDC(Pointer hdc);
/** http://msdn.microsoft.com/en-us/library/dd183488(VS.85).aspx */
public static native Pointer CreateCompatibleBitmap(Pointer hdc, int nWidth, int nHeight);
/** http://msdn.microsoft.com/en-us/library/dd162957(VS.85).aspx */
public static native Pointer SelectObject(Pointer hdc, Pointer hgdiobj);
/** http://msdn.microsoft.com/en-us/library/dd145078(VS.85).aspx */
public static native int SetPixel(Pointer hdc, int X, int Y, int crColor);
/** http://msdn.microsoft.com/en-us/library/dd144909(VS.85).aspx */
public static native int GetPixel(Pointer hdc, int nXPos, int nYPos);
/** http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx */
public static native boolean DeleteObject(Pointer hObject);
/** http://msdn.microsoft.com/en-us/library/dd183533(VS.85).aspx */
public static native boolean DeleteDC(Pointer hdc);
}
}
class WindowInfo
{
String title;
HWND hWnd;
Image image;
public WindowInfo(String title, HWND hWnd, Image image)
{
this.title = title;
this.hWnd = hWnd;
this.image = image;
}
}