Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replacements needed for the last vestiges of JNA #1107

Open
caprica opened this issue Feb 5, 2022 · 13 comments
Open

Replacements needed for the last vestiges of JNA #1107

caprica opened this issue Feb 5, 2022 · 13 comments

Comments

@caprica
Copy link
Owner

caprica commented Feb 5, 2022

For the FFI version of vlcj, one of the goals is to completely remove the JNA dependency.

One sticking point is going to be getting an adequate replacement for Native.getComponentID(). This is used to get a native window handle on at least Linux and Windows (macOS has many issues around this) that can be passed to the appropriate LibVLC native method to cause video to be rendered in the window referenced by that handle.

That Java call has a native counterpart in JNA:

https://github.com/java-native-access/jna/blob/a0641dd928776a8e81478015a1f14a39326887f3/native/dispatch.c#L3061

An alternate solution that does not use JNA is needed.

JNI is not an option here, but of course we could now use FFI to get that handle... somehow...

@caprica
Copy link
Owner Author

caprica commented Feb 10, 2022

Also need Native.getWindowID(), although this is basically the same as Native.getComponentID().

A robust replacement to get a Windows registry key is also needed.

@caprica
Copy link
Owner Author

caprica commented Feb 12, 2022

In the short term, the JNA dependency is being factored to a separate module here:

https://github.com/caprica/vlcj-legacy

The goal is to replace those functions in the new FFI vlcj itself.

@caprica caprica changed the title Replacement needed for JNA's Native.getComponentID() method Replacement needed for the last vestiges of JNA Feb 17, 2022
@caprica caprica changed the title Replacement needed for the last vestiges of JNA Replacements needed for the last vestiges of JNA Feb 19, 2022
@acely
Copy link

acely commented Jul 4, 2022

For macos, here's some code I've tried and work for vlc well:

	/**
	 * Component can be awt Canvas,Panel,Window,Frame,Dialog
	 * This method is only for apple java 6 on MacOSX, which uses
	 * apple.awt.CComponent and it's subclasses.
	 * @param c must be awt component
	 * @return heavyweight component id
	 */
	public static long getComponentNativeID_Mac_AppleJ6(Component c) {
		try {
			Method getPeer = Component.class.getMethod("getPeer");
			Object peer = getPeer.invoke(c);
			Method getViewPtr = peer.getClass().getMethod("getViewPtr");
			Object ptr = getViewPtr.invoke(peer);
			return (Long) ptr;
		} catch (Exception e) {}
		return 0;
	}
	
	/**
	 * Jre8 get awt window native ID
	 * @param window
	 * @return 0 when error
	 */
	public static long getComponentNativeID_Mac_J8(Window window) {
	    try {
	        Object componentPeer = window.getClass().getMethod("getPeer").invoke(window);
	        Object platformWindow = componentPeer.getClass().getMethod("getPlatformWindow").invoke(componentPeer);
	        Object contentView = platformWindow.getClass().getMethod("getContentView").invoke(platformWindow);
	        return (Long) contentView.getClass().getMethod("getAWTView").invoke(contentView);
	    } catch (Exception exception) {
	        return 0;
	    }
	}
	
	/**
	 * Jre11 get awt window native ID (Also works for jre8)
	 * For MacOS
	 * @param c must be awt Window
	 * @return heavyweight component id
	 * @since 2022-01-14
	 */
	public static long getComponentNativeID_Mac_J11(Window window) {
		try {
			ComponentPeer peer = AWTAccessor.getComponentAccessor().getPeer(window);
			if (!(peer instanceof LWWindowPeer)) return 0;

			Object platformWindow = ((LWWindowPeer) peer).getPlatformWindow();
			Method getContentView = platformWindow.getClass().getMethod("getContentView");
			Object contentView = getContentView.invoke(platformWindow);
			Method getAwtView = contentView.getClass().getMethod("getAWTView");
			long awtviewid = (Long) getAwtView.invoke(contentView);
			//System.out.println("getAWTView="+awtviewid);
			return awtviewid;
		}catch (Exception e) {
			return 0;
		}
	}

For jre8 and jre11, i'm using https://github.com/JetBrains/JetBrainsRuntime

@caprica
Copy link
Owner Author

caprica commented Jul 4, 2022

I had code similar to this a long time ago :-)

But, aren't these still issues with such an approach?:

  1. on macOS it's no longer any practical use since it's only for Java 1.6 or earlier.
  2. on JDK 18+ (which is what this issue is really about, since it will use the new FFI API) these reflection hacks don't work anymore, at least not without messing around with module settings, or setting a whole load of command-line switches at run-time.

So I don't think a pure Java approach is really possible with modern JDK's is it?

I was really hoping there could at least be an FFI approach, not using JNI, but from what I can see it simply is not possible to use libjawt without FFI.

JNA uses JNI in its bespoke native library to get window handles, but I really don't want to do that.

@caprica
Copy link
Owner Author

caprica commented Jul 4, 2022

@acely Your macOS code on JDK 11, it can only work with AWT Window, right? Not Canvas?

@acely
Copy link

acely commented Jul 4, 2022

Yes, works on jdk11 ONLY with awt window, using this method getComponentNativeID_Mac_J11
Dont need canvas.

Since this works with jdk11, I assume it will work for jdk 18 as well. But my os is too old to run jdk18, so cant test that(sad)

@caprica
Copy link
Owner Author

caprica commented Jul 5, 2022

I tried a reflection-based approach in JDK 18 and JDK 19ea and it did not work - it might work if you set all the right module command-line switches to open everything up, but that is not really a nice solution for developers using vlcj IMO.

@acely
Copy link

acely commented Jul 5, 2022

Agree, but just for fun I tried something, it works but not elegant at all.

public static void main(String[] args) throws Exception {
  Window window = new Window(null);
  window.setBounds(100, 100, 600, 300);
  window.setAlwaysOnTop(true);
  window.setBackground(Color.DARK_GRAY);
  window.setVisible(true);
  
  Point location = window.getLocationOnScreen();
  location.translate(2, 2);
  Robot tempRobot = new Robot();
  tempRobot.mouseMove(location.x, location.y);
  Thread.sleep(100);
  
  Class llwwp = Class.forName("sun.lwawt.LWWindowPeer");
  Method getwuc = llwwp.getMethod("getWindowUnderCursor");
  Object windowpeer = getwuc.invoke(null);//sun.lwawt.LWWindowPeer
  Method getPlatformWindow = windowpeer.getClass().getMethod("getPlatformWindow");
  Object platformwindow = getPlatformWindow.invoke(windowpeer);//sun.lwawt.macosx.CPlatformWindow
  Method getContentView = platformwindow.getClass().getMethod("getContentView");
  Object contentView = getContentView.invoke(platformwindow);
  Method getAwtView = contentView.getClass().getMethod("getAWTView");
  long awtviewid = (Long) getAwtView.invoke(contentView);
  System.out.println("getAWTView="+awtviewid);
}

The reason why I did not use AWTAccessor approach because it never returns valid result in jdk18.

I think maybe on macos we should use javafx instead.

@caprica
Copy link
Owner Author

caprica commented Jul 5, 2022

On macOS it will always be suboptimal no matter what you do simply because there is no AWT anymore.

So on macOS, I agree, use JavaFX - or maybe the new OpenGL renderer, but I don't really want to use OpenGL to write vlcj applications.

I'm all for alternate approaches, now (well, soon) that we have FFI I would be happy with any approach that used native code, as long it was callable via FFI.

@caprica caprica pinned this issue Aug 6, 2022
@altrisi
Copy link

altrisi commented Nov 26, 2022

Maybe by asking in the jdk mailing lists or making an enhancement bugreport you can get it exposed if it's there anyway and it's useful.

@caprica
Copy link
Owner Author

caprica commented Dec 3, 2022

It's extremely unlikely this will ever be exposed by the JDK. This is viewed as a private internal implementation detail.

I would have thought if it were possible/feasible, the JNA people would have already done this.

@mainrs
Copy link

mainrs commented May 16, 2024

May I ask why JNI is not an option?

@caprica
Copy link
Owner Author

caprica commented May 16, 2024

Well, it is, and that's what JNA uses. I do not want to use that because I don't want to build a native library and ship it with vlcj for all the various architectures.

Ideally, you could use JNA itself or the new FFM API but the underlying API you need to grab the component id is accessed opaquely via a JVM pointer, you can't just execute native API calls against libjawt or whatever.

Personally, I don't see the problem with the JVM/JDK exposing the native window handle - I mean I understand why they don't, it's an implementation detail, but I don't see the harm in exposing a read only handle with no guarantees. The JVM/JDK maintainers would disagree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants