Question

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:

  1. 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.

  2. 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.

  3. 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;
    }
}
Était-ce utile?

La solution

I've found a work-around that will do for my purposes. It's much more straightforward than my first attempt! I am now getting the icons using sun.awt.shell.ShellFolder which unfortunately is an undocumented/unsupported class that may be removed in future Java releases. There is another method to get the icon using FileSystemView but the icon returned is too small for my purposes (it's commented out in my example below - getImageForWindowIcon method).

This work-around is based on this SO answer by aleroot. I get the process file path (the exe file that was used to open the window, which I store in a WindowInfo object with other window details), then I use ShellFolder to get the icon associated with that file. Note: This does not always give the right icon (e.g. the file used to run the Netbeans process is java.exe so you get the Java icon, not the Netbeans one!). For the most part though, it works well!

The work-around solves questions 1 & 2 above but if anybody has any better solutions, please let me know. I didn't get anywhere with question 3 but the window list I have will have to do for now.

Here is my updated code... Note: JNA and Platform jars are required to run this example:

    package test;

import static test.WindowSwitcher.User32DLL.*;
import static test.WindowSwitcher.Kernel32.*;
import static test.WindowSwitcher.Psapi.*;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.ptr.PointerByReference;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
import sun.awt.shell.ShellFolder;

public class WindowSwitcher
{
    public static void main(String args[])
    {
        new WindowSwitcher();
    }
    public WindowSwitcher()
    {
        JFrame JF = new JFrame("Window Switcher");
        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);
    }

    private 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 (and other info)
                        WindowInfo info = getWindowTitleAndProcessDetails(hWnd);

                        // Make sure the text is not null or blank
                        if(!(info.title == null || info.title.trim().equals("")))
                        {
                            // Get the icon image for the window (if available)
                            info.image = getImageForWindow(info);

                            // This window is a valid taskbar button, add a WindowInfo object to the return vector
                            V.add(info);
                        }
                    }
                }

                return true;
            }
        }, null);

        return V;
    }

    private static final int MAX_TITLE_LENGTH = 1024;
    private WindowInfo getWindowTitleAndProcessDetails(HWND hWnd)
    {
        if(hWnd == null)
            return null;

        char[] buffer = new char[MAX_TITLE_LENGTH * 2];
        GetWindowTextW(hWnd, buffer, MAX_TITLE_LENGTH);
        String title = Native.toString(buffer);

        PointerByReference pointer = new PointerByReference();
        GetWindowThreadProcessId(hWnd, pointer);    //GetForegroundWindow()
        Pointer process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pointer.getValue());
        GetModuleBaseNameW(process, null, buffer, MAX_TITLE_LENGTH);
        String Sprocess = Native.toString(buffer);
        GetModuleFileNameExW(process, null, buffer, MAX_TITLE_LENGTH);
        String SprocessFilePath = Native.toString(buffer);

        return new WindowInfo(title, Sprocess, SprocessFilePath, hWnd, null);
    }

    private Image getImageForWindow(WindowInfo info)
    {
        if(info.processFilePath == null || info.processFilePath.trim().equals(""))
            return null;

        try
        {
            File f = new File(info.processFilePath);

            if(f.exists())
            {
                // https://stackoverflow.com/questions/10693171/how-to-get-the-icon-of-another-application
                // http://www.rgagnon.com/javadetails/java-0439.html
                ShellFolder sf = ShellFolder.getShellFolder(f);
                if(sf != null)
                    return sf.getIcon(true);

                // Image returned using this method is too small!
                //Icon icon = FileSystemView.getFileSystemView().getSystemIcon(f);
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

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

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

        public static native int GetModuleFileNameExW(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 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);
    }
}
class WindowInfo
{
    String title, process, processFilePath;
    HWND hWnd;
    Image image;

    public WindowInfo(String title, String process, String processFilePath, HWND hWnd, Image image)
    {
        this.title = title;
        this.process = process;
        this.processFilePath = processFilePath;
        this.hWnd = hWnd;
        this.image = image;
    }
}

Autres conseils

Ugly method

ShellFolder sf = ShellFolder.getShellFolder(new File("."));
Method m = sf.getClass().getDeclaredMethod("makeIcon", Long.TYPE, Boolean.TYPE);
m.setAccessible(true);
Image image = (Image) m.invoke(null, Long.valueOf(iconHandle), Boolean.TRUE);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top