3

I'm trying to configure a custom window procedure and it works. However, after a while, the window stops reacting to any input. It seems that the more rendering is going on in a scene, the sooner the window gets broken.

This even happens if my custom window procedure simply calls the default window.

Reproducer:

package com.example;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.BaseTSD;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinUser;
import com.sun.jna.win32.W32APIOptions;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.stage.Stage;

import static com.sun.jna.platform.win32.WinUser.GWL_WNDPROC;

public class CustomWndProc {

  public static void main(String[] args) {
    CustomFrameApplication.launch(CustomFrameApplication.class, args);
  }

  public static class CustomFrameApplication extends Application {

    @Override
    public void start(Stage primaryStage) {
      primaryStage.setScene(new Scene(new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS)));
      primaryStage.show();

      HWND hwnd = new HWND();
      hwnd.setPointer(User32.INSTANCE.GetActiveWindow().getPointer());

      BaseTSD.LONG_PTR defaultWindowProc = User32.INSTANCE.GetWindowLongPtr(hwnd, GWL_WNDPROC);

      WinUser.WindowProc windowProc = (hwnd1, uMsg, wParam, lParam) ->
        User32Ex.INSTANCE.CallWindowProc(defaultWindowProc, hwnd1, uMsg, wParam, lParam);

      User32Ex.INSTANCE.SetWindowLongPtr(hwnd, GWL_WNDPROC, windowProc);
    }
  }

  public interface User32Ex extends User32 {
    User32Ex INSTANCE = Native.load("user32", User32Ex.class, W32APIOptions.DEFAULT_OPTIONS);

    Pointer SetWindowLongPtr(HWND hWnd, int nIndex, WindowProc wndProc);

    LRESULT CallWindowProc(LONG_PTR proc, HWND hWnd, int uMsg, WPARAM uParam, LPARAM lParam);
  }
}

Give it a few seconds or minutes and you won't be able to move, minimize, maximize or close the window anymore.

If you want guaranteed freeze, use a WebView instead of a ProgressIndicator:

      WebView webView = new WebView();
      webView.getEngine().load("https://www.google.com");
      primaryStage.setScene(new Scene(webView));

I wondered if it has something to do that my code runs in the JavaFX Application thread (leading to some race condition) but I assume so does the default window procedure (how can I verify?).

I'm trying to build a JavaFX application that uses a custom frame.

Using JNA 5.5.0.

Michel Jung
  • 2,966
  • 6
  • 31
  • 51
  • Are you sure that `GetActiveWindow()` is returning the correct `HWND` you are expecting? Also, consider using [`SetWindowSubclass()`](https://learn.microsoft.com/en-us/windows/win32/api/commctrl/nf-commctrl-setwindowsubclass) instead of `SetWindowLongPtr()`, see [Safer Subclassing](https://devblogs.microsoft.com/oldnewthing/20031111-00/?p=41883) – Remy Lebeau Jun 19 '20 at 19:31
  • Is your window hierarchy composed of windows owned by different threads? – IInspectable Jun 19 '20 at 19:45
  • @RemyLebeau I've successfully implemented various functionality and using the debugger, I see that the window procedure is called as expected - until it's not called anymore. So I'd say yes. I tried another way to get the handle as well as `SetWindowSubclass`; same result. @IInspectable as in the example above, there's only one window so I'd say there is no hierarchy? and the output of `GetWindowThreadProcessId()` and `GetCurrentThreadId()` return the same thread ID. – Michel Jung Jun 20 '20 at 13:46
  • @MichelJung which version of jna api are you using? – micpog90 Jun 25 '20 at 13:56
  • @micpog90 JNA 5.5.0. Thanks, I added this to the question now. – Michel Jung Jun 25 '20 at 15:05

1 Answers1

1

I think this is the right way to do it, but maybe I'm wrong.. I got the Information from: Why my JNA using application doesn't react in a right way?

import com.sun.jna.LastErrorException;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.BaseTSD;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.win32.W32APIOptions;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.stage.Stage;


public class CustomWndProc {

    public static void main(String[] args) {
        CustomFrameApplication.launch(CustomFrameApplication.class, args);
    }

    public static class CustomFrameApplication extends Application {

        private BaseTSD.LONG_PTR baseWndProc;

        public User32Ex.WNDPROC listener = new User32Ex.WNDPROC() {
            public WinDef.LRESULT callback(HWND hWnd, int uMsg, WinDef.WPARAM uParam,
                    WinDef.LPARAM lParam) {
                // TODO handle the window message
                // calling the base WndProc
                return User32Ex.INSTANCE.CallWindowProc(baseWndProc, hWnd, uMsg, uParam, lParam);
            }
        };

        @Override
        public void start(Stage primaryStage) {
            primaryStage.setScene(new Scene(new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS)));
            primaryStage.show();

            HWND hwnd = new HWND();
            hwnd.setPointer(User32.INSTANCE.GetActiveWindow().getPointer());

            this.baseWndProc = User32Ex.INSTANCE.GetWindowLongPtr(hwnd, User32Ex.GWL_WNDPROC);

            User32Ex.INSTANCE.SetWindowLongPtr(hwnd, User32Ex.GWL_WNDPROC, this.listener);
        }
    }

    public interface User32Ex extends User32 {

        User32Ex INSTANCE = Native.load("user32", User32Ex.class, W32APIOptions.DEFAULT_OPTIONS);

        interface WNDPROC extends StdCallCallback {

            LRESULT callback(HWND hWnd, int uMsg, WPARAM uParam, LPARAM lParam);
        }

        LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex) throws LastErrorException;

        LRESULT CallWindowProc(LONG_PTR proc, HWND hWnd, int uMsg, WPARAM uParam, WinDef.LPARAM lParam) throws LastErrorException;

        LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, WNDPROC wndProc) throws LastErrorException;
    }
}
micpog90
  • 106
  • 8
  • This is basically the exact same code, except for `throws LastErrorException` and some more cumbersome `baseWndProc` – Michel Jung Jun 26 '20 at 17:23
  • @MichelJung You are partly right..in your code the SetWindowLongPtr() leads to the freezing of the window (wrong implementation of the callback?). This example does not freeze the window and if you think it is cumbersome, than you can try to clean it up (maybe you will find your error during cleanup). But it is not the "exact same code", else the window would also freeze.. – micpog90 Jun 26 '20 at 17:34
  • 1
    Oooh, you're right! It's because my code wasn't keeping a reference to my custom window procedure, it got garbage collected - resulting in the freeze. Wouldn't have figured that out without you :-) If you could rephrase your answer to include this, I'll be happy to grant you the bounty. Thank you! – Michel Jung Jun 26 '20 at 18:48