1

I am writing an OSX Quick-look alternative for Windows using Java and was having trouble on how to get the active selections of file in a n active Explorer window, below is my attempt:

    @Override
    public void nativeKeyReleased(NativeKeyEvent e) {
        System.out.println("key up:"
                + NativeKeyEvent.getKeyText(e.getKeyCode()));
        if (e.getKeyCode() == NativeKeyEvent.VK_SPACE) {
            System.out.println("Space detected! intercept active window");
            char[] buffer = new char[MSWindowConstants.MAX_TITLE_LENGTH * 2];
            User32DLL.GetWindowTextW(User32DLL.GetForegroundWindow(),
                    buffer, MSWindowConstants.MAX_TITLE_LENGTH);
            System.out.println("Active window title: "
                    + Native.toString(buffer));

            PointerByReference pointer = new PointerByReference();
            User32DLL.GetWindowThreadProcessId(
                    User32DLL.GetForegroundWindow(), pointer);
            Pointer process = Kernel32.OpenProcess(
                    Kernel32.PROCESS_QUERY_INFORMATION
                            | Kernel32.PROCESS_VM_READ, false,
                    pointer.getValue());
            Psapi.GetModuleBaseNameW(process, null, buffer,
                    MSWindowConstants.MAX_TITLE_LENGTH);
            System.out.println("Active window process: "
                    + Native.toString(buffer));

            if(MSWindowConstants.SHELL_PROCESS_NAME.equals(Native.toString(buffer))){
                System.out.println("shell focused! intercept selection");
                // retrieve selected FileItems and get the path ...

                //Ole32.INSTANCE

            }



        }

The MSEnumeration class:

public class MSEnumeration {



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

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

public static class Kernel32 {
    static {
        Native.register("kernel32");
    }
    public static int PROCESS_QUERY_INFORMATION = 0x0400;
    public static int PROCESS_VM_READ = 0x0010;

    public static native int GetLastError();

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

public 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);
}

//  public static class Shell32DLL{
//      static {
//          Native.register("shell32");
//      }
//      
//      public static native Shell32 Windows();
//  }
//  
//  public static class SHDocVwDLL{
//      static {
//          Native.register("shdocvw");
//      }
//      
//      public static native ShellWindows ShellWindows();
//      
//  }

}

I was confused by how to implement the following in JNA:

Get current selection in WindowsExplorer from a C# application?

IntPtr handle = GetForegroundWindow();

List<string> selected = new List<string>();
var shell = new Shell32.Shell();
foreach(SHDocVw.InternetExplorer window in shell.Windows())
{
    if (window.HWND == (int)handle)
    {
        Shell32.FolderItems items =     ((Shell32.IShellFolderViewDual2)window.Document).SelectedItems();
        foreach(Shell32.FolderItem item in items)
        {
            selected.Add(item.Path);
        }
    }
}

How can I translate this into JNA calls?

I looked into JNA'S Shell32 class and COM(Ole32 classes) but that still didn't get me anywhere.

The only workaround I can think of now is to compile the given C# into a separate executable that takes arguments and return the paths of the files, but I don't really like the idea of embedding another executable in java.

EDIT:

Some progress:

public static final String CLSID_ShellWindows = "9BA05972-F6A8-11CF-A442-00A0C90A8F39";

public static final String IID_IShellWindows = "85CB6900-4D95-11CF-960C-0080C7F4EE85";

HRESULT hr = Ole32.INSTANCE
                        .CoCreateInstance(
                                GUID.fromString(CLSID_ShellWindows),
                                null,
                                WTypes.CLSCTX_ALL,
                                GUID.fromString(IID_IShellWindows),
                                p);

                System.out.println("result:" + W32Errors.SUCCEEDED(hr)
                        + "raw:" + hr.toString());

but the result is never true for some reason...

Community
  • 1
  • 1
tom91136
  • 8,662
  • 12
  • 58
  • 74
  • Raymond Chen already answered this: [Querying information from an Explorer window](http://blogs.msdn.com/b/oldnewthing/archive/2004/07/20/188696.aspx). – IInspectable Jan 07 '14 at 14:20
  • Yes, I tried to replicate his code in Java, but `W32Errors.SUCCEEDED(hr)` never returns true – tom91136 Jan 07 '14 at 15:10
  • The error code (hr) has error information encoded into it. It usually can tell you what's wrong. You may have failed to initialize COM on the calling thread, or you may have a bitness mismatch, or some of the parameters are wrong. See [`CoCreateInstance`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms686615.aspx) for common error codes. – IInspectable Jan 07 '14 at 15:53
  • I can't figure out what does `-2147221008(HRESULT)` mean, it's not in the documentation, another strange thing is that, no matter what the CLSID is, as long as the length is correct, it will return `-2147221008`. Any ideas? – tom91136 Jan 08 '14 at 04:58
  • -2147221008 = 0x800401F0 = CoInitialize has not been called. – user2120666 Feb 19 '14 at 13:55
  • did you find a solution Tom? – amone Apr 23 '15 at 15:16
  • Yes, let me write a proper solution – tom91136 Apr 23 '15 at 21:59

1 Answers1

2

I gave up with JNA(well, for this specific task) and used com4j (with excellent documentations) instead.

You would first generate codes for the dll you want, in this case it's shell32.dll, using tlbimp.jar from com4j

This example might be a bit outdated but I'll put it here anyway

if (isExplorer(getHWNDProcessName(hwnd))) {
        IWebBrowser2 browser = getIWebBrowser2(hwnd);
        IShellFolderViewDual3 view = getIShellFolderViewDual3(browser);
        if (view != null && browser.visible()) {

            lastHWND = hwnd;
            FolderItems items = view.selectedItems();
            ArrayList<Path> paths = new ArrayList<>(items.count());
            for (Com4jObject object : items) {
                FolderItem item = object.queryInterface(FolderItem.class);
                if (item != null) {
                    paths.add(Paths.get(item.path()));
                    // this is for example only, do not create a new File just to get length
                    System.out.println("file: " + item.path() + " length: "
                            + new File(item.path()).length() + " type:" + item.type());
                }
            }
        }
    }


// some methods used in the example...

public static IWebBrowser2 getIWebBrowser2(HWND hWnd) {
    // TODO this can be potentially optimized
    IShellWindows windows = ClassFactory.createShell().windows()
            .queryInterface(IShellWindows.class);
    for (Com4jObject window : windows) {
        IWebBrowser2 browser = window.queryInterface(IWebBrowser2.class);
        if (browser.hwnd() == getHWNDValue(hWnd))
            return browser;
    }
    return null;
}

public static IShellFolderViewDual3 getIShellFolderViewDual3(IWebBrowser2 browser) {
    if (browser == null)
        return null;
    return browser.document().queryInterface(IShellFolderViewDual3.class);
}

Note: some method calls might be missing, I only posted essential parts on how to get the selected items.

IMPORTANT

You will need both Shell32.dll and Shdocvw.dll for this, so what you want to do is generate codes twice with different dlls

java -jar tlbimp.jar -o wsh -p test.wsh %WINDIR%\system32\Shell32.dll
java -jar tlbimp.jar -o wsh -p test.wsh %WINDIR%\system32\Shdocvw.dll

So that we can have IWebBrowser2 and other nice stuff to work with, for a list of what you can do with this class, please refer to the docs

tom91136
  • 8,662
  • 12
  • 58
  • 74
  • thank you very much for your answer. I generated codes but it didn't create IWebBrowser2 and IShellWindows classes. "java -jar tlbimp.jar -o wsh -p test.wsh %WINDIR%\system32\Shell32.dll". And createShell method is missing. I'll be very happy if you share how you initialize com4j? – amone Apr 23 '15 at 23:45
  • Sorry, forgot about that, details added – tom91136 Apr 24 '15 at 01:42
  • Thank you very much. You really helped me. I solved everything except one thing; where can I find HWND? JNA contains it and I tried this code with JNA's "com.sun.jna.platform.win32.WinDef.HWND" class and it works great. What should I do? Thank you very much again. – amone Apr 24 '15 at 12:57
  • I solved. But I am getting this error "com4j.ComException: 80004005 .\invoke.cpp:517" are you getting this? – amone Apr 24 '15 at 15:07
  • Are you calling getIWebBrowser on a non-explorer window? Which version of Windows and java are you using? – tom91136 Apr 24 '15 at 15:12
  • Windows 7 and java 1.6. I tried only with Windows Explorer, it throws this error after 5-10 tries. But shows the correct results. – amone Apr 24 '15 at 15:43
  • Post a new question with your code and steps, this could possibly help more people who are having the same issue. (link back to this question so that others know where you started) – tom91136 Apr 24 '15 at 15:46
  • Occurs only If I close a window. http://stackoverflow.com/questions/29852443/error-com4j-comexception-80004005-invoke-cpp51-while-getting-open-windows-an – amone Apr 24 '15 at 16:11
  • OK, will take a look at it went I'm home – tom91136 Apr 24 '15 at 16:17