我正在写一种Web Applet模拟器。 我读了一个网页,find小程序参数,下载小程序并运行它。 applet运行在自己的进程中(即不是模拟器进程)是非常重要的。 但是,它应该在模拟器进程窗口中呈现。
Java插件如何做到这一点? 当设置了separate_jvm
标志时,插件会在单独的JVM进程中加载小程序,但是小程序仍会出现在同一个浏览器面板中。
我已经通过创build一个loader类来取得一些进展,在另一个JVM上,它将目标Applet添加到未修饰的不可见框架,并将框架的窗口句柄消息发送到仿真器JVM。 后者通过JNA将其绑定到一个具有user32.SetParent
的Canvas
实例,并且显示效果很好。
但是,只有鼠标事件被发送:键盘input不被转发。 小程序将Component#isFocusOwner
报告为false,并且requestFocusInWindow
不会使其成为焦点所有者,并返回false。 如何将键盘焦点传递给Applet窗口句柄? 我目前的方法涉及一个服务器(模拟器),从客户端(小程序)接收窗口句柄。 只有鼠标事件才能起作用,因为Applet无法获得焦点。
服务器类处理小程序的显示。
import com.sun.jna.*; import com.sun.jna.platform.win32.User32; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import static com.sun.jna.platform.win32.User32.*; public class Loader { private static final String APPLET_DIRECTORY = ""; // TODO: Set this to the directory containing the compiled applet private static ServerSocket serverSocket; private static JFrame frame; private static Canvas nativeDisplayCanvas; public static void main(String[] argv) throws Exception { nativeDisplayCanvas = new Canvas(); frame = new JFrame("Frame redirect"); frame.setLayout(new BorderLayout()); frame.add(nativeDisplayCanvas, BorderLayout.CENTER); frame.setSize(300, 200); frame.setLocationRelativeTo(null); frame.setVisible(true); (new Thread() { public void run() { try { serve(); } catch (Exception e) { e.printStackTrace(); } } }).start(); spawnAltJVM(APPLET_DIRECTORY, "AppletDemo"); } public static void serve() throws Exception { serverSocket = new ServerSocket(6067); serverSocket.setSoTimeout(10000); while (true) { try { System.out.println("Waiting for applet on port " + serverSocket.getLocalPort() + "..."); Socket server = serverSocket.accept(); System.out.println("Connected to " + server.getRemoteSocketAddress()); BufferedReader in = new BufferedReader(new InputStreamReader(server.getInputStream())); DataOutputStream out = new DataOutputStream(server.getOutputStream()); while (true) { String msg = in.readLine(); if (msg != null && msg.startsWith("child_hwnd")) { windowCreatedHandler(msg); out.writeUTF("hwnd_recv\n"); out.flush(); } } } catch (IOException ex) { System.out.println("Something happened to the socket..."); break; } } } public static void windowCreatedHandler(String message) { String[] tokens = message.split(":"); final User32 user32 = User32.INSTANCE; HWND child_applet = new HWND(Pointer.createConstant(Long.parseLong(tokens[1]))); final HWND child_frame = new HWND(Pointer.createConstant(Long.parseLong(tokens[2]))); frame.addComponentListener( new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { user32.SetWindowPos(child_frame, new HWND(Pointer.NULL), 0, 0, frame.getWidth(), frame.getHeight(), 0); } } ); HWND parent = new HWND(Native.getComponentPointer(nativeDisplayCanvas)); user32.SetParent(child_applet, parent); int style = user32.GetWindowLong(child_frame, GWL_STYLE) & ~WS_POPUP | (WS_CHILD | WS_VISIBLE); user32.SetWindowLong(child_applet, GWL_STYLE, style); user32.SetWindowPos(child_applet, new HWND(Pointer.NULL), 0, 0, nativeDisplayCanvas.getWidth(), nativeDisplayCanvas.getHeight(), 0); } public static void spawnAltJVM(String cp, String clazz) throws IOException, InterruptedException, ClassNotFoundException { ProcessBuilder processBuilder = new ProcessBuilder(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java", "-cp", cp, clazz); Process applet = processBuilder.start(); final BufferedReader in = new BufferedReader(new InputStreamReader(applet.getInputStream())); final BufferedReader err = new BufferedReader(new InputStreamReader(applet.getErrorStream())); (new Thread() { public void run() { while (true) { try { System.out.println("[client] " + in.readLine()); } catch (IOException e) { e.printStackTrace(); } } } }).start(); } }
同时,客户端类实例化消息句柄。
import sun.awt.windows.WComponentPeer; import javax.swing.*; import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.net.Socket; import java.util.concurrent.LinkedBlockingDeque; public class AppletDemo extends Applet { private Canvas canvas; private static Color backgroundColor = Color.RED; public AppletDemo() { setLayout(new BorderLayout()); canvas = new Canvas(); add(canvas, BorderLayout.CENTER); setBackground(Color.CYAN); canvas.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { refreshColors(); } }); canvas.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { refreshColors(); } }); } private void refreshColors() { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { backgroundColor = (backgroundColor == Color.RED ? Color.GREEN : Color.RED); canvas.setBackground(backgroundColor); } } ); } public static void main(String[] argv) throws Exception { System.setErr(System.out); final AppletDemo app = new AppletDemo(); Frame frame = new Frame("AppletViewer"); frame.setLayout(new BorderLayout()); frame.add(app, BorderLayout.CENTER); frame.setUndecorated(true); frame.pack(); // Create the native peers frame.setSize(300, 200); final Socket client = new Socket("localhost", 6067); final LinkedBlockingDeque<String> messageQueue = new LinkedBlockingDeque<>(); final DataOutputStream out = new DataOutputStream(client.getOutputStream()); final BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); (new Thread() { public void run() { while (true) { try { out.writeBytes(messageQueue.take() + "\n"); out.flush(); } catch (IOException | InterruptedException ex) { ex.printStackTrace(); } } } }).start(); (new Thread() { public void run() { while (true) { try { if ("hwnd_recv".equals(in.readLine())) { // Attempt to grab focus in the other process' frame System.out.println("Trying to request focus..."); System.out.println(app.requestFocusInWindow()); } } catch (IOException ex) { ex.printStackTrace(); } } } }).start(); messageQueue.add("child_hwnd:" + ((WComponentPeer) app.getPeer()).getHWnd() + ":" + ((WComponentPeer) frame.getPeer()).getHWnd()); } }
它们都有点冗长,因为它们需要一些套接字的工作,但是它们是可编译的并且应该certificate这个问题。 他们要求JNA编译。 我尽可能地缩短了他们的代价,这是以一些好的做法为代价的。
当Loader
运行时,会出现一个redirectAppletDemo
canvas的窗口。 鼠标事件被发送:canvas在鼠标按下时在红色和绿色之间切换。 理想情况下,键击也应该发生同样的行为。
我已经使用WinSpy获取notepad.exe窗口和文本窗格的句柄,并将这些句柄硬编码到Loader
。 键盘焦点与多行编辑控件完美配合,但与顶级窗口本身无关。 为什么? 这与我遇到的问题有关吗?
我打开了一个在WinSpy中运行一个applet的Chrome窗口,发现这个插件没有创build虚拟Frame
– 这个appletcanvas直接设置为Chrome的一个子项。 然而,我还没有能够创build一个Applet
的本地对等,因为它似乎要求它是可显示的。
我已经读过跨进程的父/子或拥有者/拥有的窗口关系的危险 ,但是我想不出一个更好的方法将子applet移植到模拟器中。
由于您真正想要的是将applet创建为子窗口,所以简单的解决方案是将applet说服成为您的孩子,而不是强制采用它,并针对Windows和JVM进行工作。
幸运的是,Sun / Oracle Java虚拟机带有一个名为WComponentFrame
的类(仅限于Windows名称)。 它可以从一个hwnd
构建,你可以从你的父进程发送。 小程序然后可以添加为您的窗口的孩子。
import sun.awt.windows.WComponentPeer; frame = new WEmbeddedFrame(hwnd); frame.setLayout(new BorderLayout()); frame.add(applet, BorderLayout.CENTER);
看起来你正在试图把事件传递给Canvas对象,而你并没有明确地设置setFocusable(true)。
如果是这种情况,那么在你的AppletDemo构造函数中,尝试:
canvas.setFocusable(true); canvas.requestFocus();
此外,您似乎想将重要事件传递给您的Applet,而不是您的问题中的Canvas。
在这种情况下,在你的AppletDemo构造函数中试试这个:
this.setFocusable(true); this.requestFocus();
在此之后,您应该默认接收键盘输入到所关注的组件。