diff --git a/build.xml b/build.xml
index 7b7957f..af66c19 100644
--- a/build.xml
+++ b/build.xml
@@ -15,7 +15,7 @@
-
@@ -23,12 +23,16 @@
-
+
+
+
+
+
@@ -124,13 +128,14 @@
-
+
@@ -149,7 +154,7 @@
-
diff --git a/src/main/java/com/threerings/getdown/data/Properties.java b/src/main/java/com/threerings/getdown/data/Properties.java
index f33a033..f5d2d25 100644
--- a/src/main/java/com/threerings/getdown/data/Properties.java
+++ b/src/main/java/com/threerings/getdown/data/Properties.java
@@ -32,4 +32,8 @@ public class Properties
{
/** This property will be set to "true" on the application when it is being run by getdown. */
public static final String GETDOWN = "com.threerings.getdown";
+
+ /** If accepting connections from the launched application, this property
+ * will be set to the connection server port. */
+ public static final String CONNECT_PORT = "com.threerings.getdown.connectPort";
}
diff --git a/src/main/java/com/threerings/getdown/launcher/GetdownApplet.java b/src/main/java/com/threerings/getdown/launcher/GetdownApplet.java
index 7ab498c..3711f35 100644
--- a/src/main/java/com/threerings/getdown/launcher/GetdownApplet.java
+++ b/src/main/java/com/threerings/getdown/launcher/GetdownApplet.java
@@ -27,12 +27,17 @@ package com.threerings.getdown.launcher;
import java.awt.Container;
import java.awt.Image;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
+import java.net.InetAddress;
import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@@ -44,6 +49,11 @@ import java.security.cert.CertificateFactory;
import javax.swing.JApplet;
import javax.swing.JPanel;
+import netscape.javascript.JSObject;
+
+import com.threerings.getdown.data.Application;
+import com.threerings.getdown.data.Properties;
+
import static com.threerings.getdown.Log.log;
/**
@@ -52,6 +62,35 @@ import static com.threerings.getdown.Log.log;
public class GetdownApplet extends JApplet
implements ImageLoader
{
+ /**
+ * Sets the JavaScript callback to invoke when a message is received from the launched app.
+ * The callback should be a function that accepts a single string parameter (the received
+ * message).
+ */
+ public synchronized void setMessageCallback (JSObject callback)
+ {
+ _messageCallback = callback;
+ }
+
+ /**
+ * Attempts to send a message to the launched app.
+ *
+ * @return true if we succeeded in sending the message, false if the launched app has not (yet)
+ * established a connection to Getdown, or the send failed.
+ */
+ public synchronized boolean sendMessage (String message)
+ {
+ if (_connectOut != null) {
+ try {
+ _connectOut.writeUTF(message);
+ return true;
+ } catch (IOException e) {
+ log.warning("Error sending message to app.", "message", message, e);
+ }
+ }
+ return false;
+ }
+
@Override // documentation inherited
public void init ()
{
@@ -108,6 +147,19 @@ public class GetdownApplet extends JApplet
return GetdownApplet.this;
}
@Override
+ protected void launch () {
+ // if so configured, create a server socket to listen
+ // for a connection from the app
+ if (_config.allowConnect) {
+ try {
+ startConnectServer();
+ } catch (IOException e) {
+ log.warning("Failed to start connect server.", e);
+ }
+ }
+ super.launch();
+ }
+ @Override
protected void exit (int exitCode) {
_app.releaseLock();
_config.redirect();
@@ -128,6 +180,62 @@ public class GetdownApplet extends JApplet
}
}
+ /**
+ * Attempts to start the server that will accept a connection from the launched app, allowing
+ * it to exchange messages with the JavaScript context.
+ */
+ protected void startConnectServer ()
+ throws IOException
+ {
+ // bind and set a property with the local port that will be passed through to the app
+ _serverSocket = new ServerSocket(0, 0, InetAddress.getByName(null));
+ System.setProperty(Application.PROP_PASSTHROUGH_PREFIX + Properties.CONNECT_PORT,
+ String.valueOf(_serverSocket.getLocalPort()));
+ Thread thread = new Thread("ConnectServer") {
+ @Override
+ public void run () {
+ while (true) {
+ try {
+ acceptConnection();
+ } catch (IOException e) {
+ if (!_serverSocket.isClosed()) {
+ log.warning("Error accepting connection.", e);
+ }
+ break;
+ }
+ }
+ }
+ protected void acceptConnection () throws IOException {
+ Socket socket = _serverSocket.accept();
+ DataInputStream connectIn = new DataInputStream(socket.getInputStream());
+ synchronized (GetdownApplet.this) {
+ _connectOut = new DataOutputStream(socket.getOutputStream());
+ }
+ while (true) {
+ try {
+ String message = connectIn.readUTF();
+ synchronized (GetdownApplet.this) {
+ if (_messageCallback != null) {
+ _messageCallback.call("call",
+ new Object[] { _messageCallback, message });
+ }
+ }
+ } catch (IOException e) {
+ if (!socket.isClosed()) {
+ log.warning("Error reading message.", e);
+ }
+ break;
+ }
+ }
+ synchronized (GetdownApplet.this) {
+ _connectOut = null;
+ }
+ }
+ };
+ thread.setDaemon(true);
+ thread.start();
+ }
+
// implemented from ImageLoader
public Image loadImage (String path)
{
@@ -163,6 +271,25 @@ public class GetdownApplet extends JApplet
_getdown._app.releaseLock();
}
+ @Override // documentation inherited
+ public synchronized void destroy ()
+ {
+ if (_serverSocket != null) {
+ try {
+ _serverSocket.close();
+ } catch (IOException e) {
+ log.warning("Error closing server socket.", e);
+ }
+ }
+ if (_connectOut != null) {
+ try {
+ _connectOut.close();
+ } catch (IOException e) {
+ log.warning("Error closing connect socket/output stream.", e);
+ }
+ }
+ }
+
/**
* Creates the specified file and writes the supplied contents to it.
*/
@@ -207,4 +334,13 @@ public class GetdownApplet extends JApplet
/** An error encountered during initialization. */
protected String _errmsg;
+
+ /** The message callback registered by JavaScript on the containing page, if any. */
+ protected JSObject _messageCallback;
+
+ /** The server socket on which we listen for connections, if any. */
+ protected ServerSocket _serverSocket;
+
+ /** The output stream to the launched app, if a connection has been established. */
+ protected DataOutputStream _connectOut;
}
diff --git a/src/main/java/com/threerings/getdown/launcher/GetdownAppletConfig.java b/src/main/java/com/threerings/getdown/launcher/GetdownAppletConfig.java
index 9f5d73b..a0fa155 100644
--- a/src/main/java/com/threerings/getdown/launcher/GetdownAppletConfig.java
+++ b/src/main/java/com/threerings/getdown/launcher/GetdownAppletConfig.java
@@ -57,6 +57,7 @@ public class GetdownAppletConfig
public static final String ERRORBGIMAGE = "errorbgimage";
public static final String PARAM_DELIMITER = ".";
public static final String DIRECT = "direct";
+ public static final String CONNECT = "connect";
public static final String REDIRECT_ON_FINISH = "redirect_on_finish";
public static final String REDIRECT_ON_FINISH_TARGET = "redirect_on_finish_target";
@@ -77,7 +78,7 @@ public class GetdownAppletConfig
* }
* When a number is reached for which no value exists, we stop looking. */
public static final String APPARG_PREFIX = "appargs";
-
+
public String appbase;
public String appname;
@@ -107,6 +108,11 @@ public class GetdownAppletConfig
* separate java process (false). */
public boolean invokeDirect;
+ /** Indicates whether Getdown should allow the launched app to connect to it through a server
+ * socket bound to localhost on any available port in order to allow interaction with
+ * JavaScript code on the page containing the applet. */
+ public boolean allowConnect;
+
/** Optional default bounds for the status panel. */
public Rectangle statusBounds;
@@ -183,6 +189,9 @@ public class GetdownAppletConfig
String direct = getParameter(DIRECT, "false");
invokeDirect = Boolean.valueOf(direct);
+ String connect = getParameter(CONNECT, "false");
+ allowConnect = Boolean.valueOf(connect);
+
String redirectURL = getParameter(REDIRECT_ON_FINISH);
if (redirectURL != null) {
try {