我正在编写一种web applet模拟器。我阅读了一个网页,找到了applet参数,下载了applet并运行它。 applet在自己的进程中运行非常重要(即不是模拟器进程)。但是,应该在模拟器进程窗口中进行渲染。
Java插件是如何做到的?当设置separate_jvm
标志时,插件会在单独的JVM进程中加载applet,但applet仍然出现在同一个浏览器面板中。
我通过创建一个加载器类来取得一些进展,该类在另一个JVM上将目标Applet添加到未修饰的不可见帧中,并将帧的窗口句柄发送给模拟器JVM。后者通过JNA将Canvas
实例与user32.SetParent
绑定,显示效果很好。
但是,仅发送鼠标事件:不转发键盘输入。小程序将Component#isFocusOwner
报告为false,requestFocusInWindow
不会将其作为焦点所有者,返回false。 如何将键盘焦点传递给Applet窗口句柄?我当前的方法涉及服务器(模拟器),它从客户端(applet)接收窗口句柄。只有鼠标事件才有效,因为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());
}
}
它们都有点冗长,因为它们需要一些套接字工作,但它们是可编译的并且应该证明这个问题。他们需要JNA编译。我以一些好的做法为代价尽可能地缩短了它们。
运行Loader
时,应显示重定向AppletDemo
画布的窗口。发送鼠标事件:画布在鼠标按下时在红色和绿色之间切换。理想情况下,键盘也应该出现相同的行为。
我使用WinSpy来获取notepad.exe窗口和文本窗格的句柄,并将句柄硬编码到Loader
。键盘焦点与多行编辑控件完美配合,但与顶层窗口本身无关。为什么?这与我遇到的问题有关吗?
我打开了一个在WinSpy中运行applet的Chrome窗口,发现该插件没有创建虚拟Frame
- applet画布直接设置为Chrome的子项。但是,我无法为Applet
创建本地对等体,因为它似乎要求它可以显示。
我已经阅读了dangers of cross-process parent/child or owner/owned window relationship,但我想不出更好的方法将子applet移植到模拟器中。
答案 0 :(得分:1)
因为你真正想要的是将applet创建为子窗口,所以简单的解决方案就是说服applet成为你的孩子,而不是强行采用它,并且同时对抗Windows和JVM。
幸运的是,Sun / Oracle Java VM附带了一个名为WComponentFrame
的类(仅从Windows名称隐含的Windows)。它可以从hwnd
构建,您可以从父进程发送它。然后可以将小程序添加为窗口的子项。
import sun.awt.windows.WComponentPeer;
frame = new WEmbeddedFrame(hwnd);
frame.setLayout(new BorderLayout());
frame.add(applet, BorderLayout.CENTER);
答案 1 :(得分:0)
看起来您正在尝试将事件传递给Canvas对象,而您没有明确地为此设置setFocusable(true)。
如果是这种情况,那么在AppletDemo构造函数中,尝试:
canvas.setFocusable(true);
canvas.requestFocus();
此外,您似乎想要将关键事件传递给Applet而不是您的问题中的Canvas。
在这种情况下,请在AppletDemo构造函数中尝试:
this.setFocusable(true);
this.requestFocus();
之后,您应该默认接收键盘输入的键盘输入。
答案 2 :(得分:0)
使用JNA就像
HWND hwnd1 = User32.INSTANCE.FindWindow(null, "JFrame1");
HWND hwnd2 = User32.INSTANCE.FindWindow(null, "JFrame2");
HWND hwnd3 = User32.INSTANCE.SetParent(hwnd2, hwnd1);
另请参见
Good or evil - SetParent() win32 API between different processes