0

I found this question that does the opposite: embed a HWND window inside a JPanel, but it's obviously not what I need

According to Wikipedia a valid Windows screensaver supports the /p <HWND> flag to spawn the screensaver as a child of the specified window (instead of fullscreen or whatever it does by default). This is used, for example, to render the preview in the screensaver selection tool.

Side notes: I'm using Scala, so maybe there's some weird Scala API I can use. My main window is just a JFrame with

setSize(getToolkit.getScreenSize)
setUndecorated(true)

to make it full-screen.

RubenVerg
  • 145
  • 1
  • 8
  • As far as I am aware, there is no Java API for this. I managed to do it using function [SetParent](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setparent) of the Windows API but it was not very stable. Moving or resizing the native window as well as switching to a different, running application caused weird effects on the screen (as I recall). – Abra Mar 11 '21 at 08:55
  • @Abra would you mind sharing that code? It's better than nothing, and I'm not really practival with the Windows API's. – RubenVerg Mar 11 '21 at 12:29
  • I don't have it anymore. Since it did not solve my problem, I saw no reason to hang on to it. – Abra Mar 11 '21 at 13:06
  • :( well, guess I'll learn Windows' APIs then – RubenVerg Mar 11 '21 at 13:54
  • This can be done with newly released JDK16 using the new Panama APIs / Foreign memory. As well as `SetParent` as mentioned by @Abra which places your panel as child of the dialog, you also need to calls to `GetClientRect` , `GetWindowLongPtrW` and `SetWindowLongW` to ensure your preview panel moves with the Windows screensaver dialog. – DuncG Mar 28 '21 at 08:56
  • @DuncG would you mind writing an answer for that? I don't have Java 16 access but I can handle the port to JNA/JNI, just give me some idea of how it should be written – RubenVerg Mar 30 '21 at 15:20
  • I forgot to mention need to find HWND for Java Swing. See outline / pseudo code answer below. – DuncG Mar 30 '21 at 17:57

1 Answers1

2

How to make a Java Swing app as a child of Windows native HWND / Set up ScreenSaver preview panel

NOTE: The code examples here will not compile and omits the error code checks, but is just to show your the order of Windows API calls needed to dock any Java Swing frame inside Windows native HWND parent - for your own implementation in JNI /JNA or JDK16+ Panama.

Firstly work out parent Windows long hWndParent. For Windows Screensavers this is passed into the "SCR" executable as with "/C" or "/P" (upper/lower) plus parent HWND argument. It may have SPACE or ":" separating the flag and HWND, or as two arguments FLAG + HWND so make sure you parse long hWndParent handle from the number passed in to main(String[] args) as:

args = {"/P 1234567"}; 
// OR {"/c", "1234567"} 
// OR {"/c:1234567"}

Create your Java Swing UI - either JFrame or sub-class without title bars. You have to make it visible so that you can retrieve the HWND.

JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setVisible(true);

Find your Java Swing HWND long hWndChild. There are other SO posts on getting HWND of Swing components, but I call the EnumWindows API. You have to supply a callback WNDENUMPROC lpEnumFunc function to EnumWindows. My example passes in the Java process PID so it also works for ANY native Windows process ID too:

/**
 * EnumWindows function (winuser.h)
 * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows
 * https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633498(v=vs.85)
 * BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);
 */
/**
 * GetWindowThreadProcessId function (winuser.h)
 * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid
 * DWORD GetWindowThreadProcessId(HWND hWnd, LPDWORD lpdwProcessId);
 */

This is a stripped down outline of calls to retrieve HWND in JDK Panama:

class User32 {
    private final List<Long> hWnds = new ArrayList<>();
    // BOOL EnumWindowsProc(HWND hwnd, LPARAM lParam);
    int EnumWindowsCallback(long hwnd, long pidToFind) { // MemoryAddress in Panama
        // Set up lpdwProcessId+ use with:
        int threadId = (int)GetWindowThreadProcessId(hwnd, lpdwProcessId);
        long procId = // read address from lpdwProcessId
        if(procId == pidToFind) {
            hWnds.add(hwnd);
        }
        return TRUE;
    }
    long getHWND(long findPid) {
        // Set up lpEnumFunc as pointer to this.EnumWindowsCallback:
        EnumWindows(lpEnumFunc, findPid);
        return hWnds.get(0);
    }
}
long findPid = ProcessHandle.current().pid();
long hWndChild = new User32().getHWND(findPid);

Size your preview panel / Java Swing component long hWndChild to match the Windows native hWndParent:

/**
 * GetClientRect function (winuser.h)
 * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect
 * BOOL GetClientRect(HWND hWnd, LPRECT lpRect);
 */
Rectangle clientRect = // call GetClientRect(hWndParent, yourRECT) => convert to Java Rectangle ;
frame.setBounds(clientRect);

Dock your preview panel / Java Swing component long hWndChild to match the Windows native hWndParent - this ensures Z-order is correct:

dockToParent(hWndChild, hWndParent);

public static void dockToParent(long hWndChild, long hWndNewParent) {

    // Set the preview window as the parent of this window
    /**
     * Set Windows Parent
     * SetParent function (winuser.h)
     * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setparent
     * HWND SetParent(HWND hWndChild, HWND hWndNewParent);
     */
    SetParent(hWndChild, hWndNewParent);

    // Make child window so it will close when the parent dialog closes
    int GWL_STYLE = -16;
    
    /**
     * GetWindowLongPtrW function (winuser.h)
     * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw
     * LONG_PTR GetWindowLongPtrW(HWND hWnd, int  nIndex);
     */
    long gwl = (long)GetWindowLongPtrW(hWndChild, GWL_STYLE);

    // https://learn.microsoft.com/en-us/windows/win32/winmsg/window-styles
    int WS_CHILD = 0x40000000;
    
    /**
     * SetWindowLongW function (winuser.h)
     * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowlongw
     * LONG SetWindowLongW(HWND hWnd, int  nIndex, LONG dwNewLong);
     */
    int prev = (int)SetWindowLongW(hWndChild, GWL_STYLE, (int)gwl | WS_CHILD);
}

At this point, your Java Swing frame should be fixed inside the Windows HWND parent window, and it stays on top of, moves around with, and disappears when that window is closed.

DuncG
  • 12,137
  • 2
  • 21
  • 33