/* This java class is distributed under the BSD license. * * Copyright 2005 Lilian Chamontin. * contact lilian.chamontin at f r e e . f r */ /* * Portions Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. */ package javax.media.opengl.util.swing; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.Label; import java.awt.Panel; import java.applet.Applet; import java.applet.AppletStub; import java.applet.AppletContext; import java.io.*; import java.net.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.*; import java.text.*; import java.util.*; import java.util.jar.*; import javax.swing.*; import javax.media.opengl.*; /** This class enables deployment of high-end applets which use OpenGL * for 3D graphics via JOGL and (optionally) OpenAL for spatialized * audio via JOAL. The applet being deployed may be either signed or * unsigned; if it is unsigned, it runs inside the security sandbox, * and if it is signed, the user receives a security dialog to accept * the certificate for the applet as well as for JOGL and JOAL.
* * The steps for deploying such applets are straightforward. First, * the "archive" parameter to the applet tag must contain jogl.jar * and gluegen-rt.jar, as well as any jar files associated with your * applet (in this case, "your_applet.jar").
* * Second, the codebase directory on the server, which contains the * applet's jar files, must also contain jogl.jar, gluegen-rt.jar, * and all of the jogl-natives-*.jar and gluegen-rt-natives-*.jar * files from the standard JOGL and GlueGen runtime distributions * (provided in jogl-[version]-webstart.zip from the JOGL * release builds and gluegen-rt-[version]-webstart.zip from the * GlueGen * runtime release builds). Note that the codebase of the applet * is currently the location from which the JOGL native library used * by the applet is downloaded. All of the JOGL and GlueGen-related * jars must be signed by the same entity, which is typically Sun * Microsystems, Inc.
* * To deploy an applet using both JOGL and JOAL, simply add joal.jar * to the list of jars in the archive tag of the applet, and put * joal.jar and the joal-natives-*.jar signed jars into the same * codebase directory on the web server. These signed jars are * supplied in the joal-[version]-webstart.zip archive from the JOAL * release builds.
* * Sample applet code: *
* <applet code="com.sun.opengl.util.JOGLAppletLauncher" * width=600 * height=400 * codebase="/lib" * archive="jogl.jar,gluegen-rt.jar,your_applet.jar"> * <param name="subapplet.classname" VALUE="untrusted.JOGLApplet"> * <param name="subapplet.displayname" VALUE="My JOGL Applet"> * <param name="progressbar" value="true"> * <param name="cache_archive" VALUE="jogl.jar,gluegen-rt.jar,your_applet.jar"> * <param name="cache_archive_ex" VALUE="jogl.jar;preload,gluegen-rt.jar;preload,your_applet.jar;preload"> * </applet> **
* * There are some limitations with this approach. It is not possible * to specify e.g. -Dsun.java2d.noddraw=true or * -Dsun.java2d.opengl=true for better control over the Java2D * pipeline as it is with Java Web Start. However, the * JOGLAppletLauncher tries to force the use of * -Dsun.java2d.noddraw=true on Windows platforms for best robustness * by detecting if it has not been set and asking the user whether it * can update the Java Plug-In configuration automatically. If the * user agrees to this, a browser restart is required in order for the * change to take effect, though it is permanent for subsequent * browser restarts.
*
* The behavior of the noddraw-related dialog box can be changed via
* two applet parameters. The jogl.silent.noddraw.check
* parameter, if set to "true"
, silences the two dialog
* boxes associated with this check, forcing it to always be performed
* and deployment.properties to be silently updated if necessary
* (unless the user previously saw such a dialog box and dismissed it
* by saying "No, Don't Ask Again"). The noddraw check can be disabled
* completely by setting the jogl.disable.noddraw.check
* applet parameter to "true"
.
* * The JOGL (and optionally JOAL) natives are cached in the user's * home directory (the value of the "user.home" system property in * Java) under the directory .jogl_ext. The Java Plug-In is * responsible for performing all other jar caching. If the JOGL * installation is updated on the server, the .jogl_ext cache will * automatically be updated.
* * This technique requires that JOGL has not been installed in to the * JRE under e.g. jre/lib/ext. If problems are seen when deploying * this applet launcher, the first question to ask the end user is * whether jogl.jar and any associated DLLs, .so's, etc. are installed * directly in to the JRE. The applet launcher has been tested * primarily under Mozilla, Firefox and Internet Explorer; there may * be problems when running under, for example, Opera.
* * It has been discovered that the Talkback agent in Mozilla / Firefox * has bad interactions with OpenGL applets. For highest performance, * we recommend disabling the Talkback agent; find talkback.exe, run * it, and follow the directions for turning it off. Please see * this * thread on the javagaming.org forums and * this * thread on the Mozilla bug reporting database.
*
* @author Lilian Chamontin
* @author Kenneth Russell
*/
public class JOGLAppletLauncher extends Applet {
static {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ignore) {
}
}
// metadata for native libraries
private static class NativeLibInfo {
private String osName;
private String osArch;
private String osNameAndArchPair;
private String nativePrefix;
private String nativeSuffix;
public NativeLibInfo(String osName, String osArch, String osNameAndArchPair, String nativePrefix, String nativeSuffix) {
this.osName = osName;
this.osArch = osArch;
this.osNameAndArchPair = osNameAndArchPair;
this.nativePrefix = nativePrefix;
this.nativeSuffix = nativeSuffix;
}
public boolean matchesOSAndArch(String osName, String osArch) {
if (osName.toLowerCase().startsWith(this.osName)) {
if ((this.osArch == null) ||
(osArch.toLowerCase().equals(this.osArch))) {
return true;
}
}
return false;
}
public boolean matchesNativeLib(String fileName) {
if (fileName.toLowerCase().endsWith(nativeSuffix)) {
return true;
}
return false;
}
public String formatNativeJarName(String nativeJarPattern) {
return MessageFormat.format(nativeJarPattern, new Object[] { osNameAndArchPair });
}
public String getNativeLibName(String baseName) {
return nativePrefix + baseName + nativeSuffix;
}
public boolean isMacOS() {
return (osName.equals("mac"));
}
public boolean mayNeedDRIHack() {
return (!isMacOS() && !osName.equals("win"));
}
}
private static final NativeLibInfo[] allNativeLibInfo = {
new NativeLibInfo("win", "x86", "windows-i586", "", ".dll"),
new NativeLibInfo("win", "amd64", "windows-amd64", "", ".dll"),
new NativeLibInfo("win", "x86_64","windows-amd64", "", ".dll"),
new NativeLibInfo("mac", "ppc", "macosx-ppc", "lib", ".jnilib"),
new NativeLibInfo("mac", "i386", "macosx-universal", "lib", ".jnilib"),
new NativeLibInfo("linux", "i386", "linux-i586", "lib", ".so"),
new NativeLibInfo("linux", "x86", "linux-i586", "lib", ".so"),
new NativeLibInfo("linux", "amd64", "linux-amd64", "lib", ".so"),
new NativeLibInfo("linux", "x86_64","linux-amd64", "lib", ".so"),
new NativeLibInfo("sunos", "sparc", "solaris-sparc", "lib", ".so"),
new NativeLibInfo("sunos", "sparcv9","solaris-sparcv9", "lib", ".so"),
new NativeLibInfo("sunos", "x86", "solaris-i586", "lib", ".so"),
new NativeLibInfo("sunos", "amd64", "solaris-amd64", "lib", ".so"),
new NativeLibInfo("sunos", "x86_64","solaris-amd64", "lib", ".so")
};
private NativeLibInfo nativeLibInfo;
// Library names computed once the jar comes down.
// The signatures of these native libraries are checked before
// installing them.
private String[] nativeLibNames;
/** The applet we have to start */
private Applet subApplet;
private String subAppletClassName; // from applet PARAM
private String subAppletDisplayName; // from applet PARAM
/** URL string to an image used while installing */
private String subAppletImageName; // from applet PARAM
private String installDirectory; // (defines a private directory for native libs)
private JPanel loaderPanel = new JPanel(new BorderLayout());
private JProgressBar progressBar = new JProgressBar(0,100);
private boolean isInitOk = false;
/** false once start() has been invoked */
private boolean firstStart = true;
/** true if start() has passed successfully */
private boolean joglStarted = false;
/** Indicates whether JOAL is present */
private boolean haveJOAL = false;
// Helpers for question about whether to update deployment.properties
private static final String JRE_PREFIX = "deployment.javapi.jre.";
private static final String NODDRAW_PROP = "-Dsun.java2d.noddraw=true";
private static final String DONT_ASK = ".dont_ask";
public JOGLAppletLauncher() {
}
private static String md2Hash(String str) {
// Helps hash the jars in the "archive" tag into a hex value to
// avoid having too-long path names in the install directory's
// path name but also to have unique directories for each
// different archive set used (also meaning for each class loader
// loading something via the JOGLAppletLauncher) -- note that this
// is somewhat dependent on the Sun implementation of applets and
// their class loaders
MessageDigest md2 = null;
try {
md2 = MessageDigest.getInstance("MD2");
} catch (NoSuchAlgorithmException e) {
return "";
}
byte[] digest = md2.digest(str.getBytes());
if (digest == null || (digest.length == 0))
return "";
StringBuffer res = new StringBuffer();
for (int i = 0; i < digest.length; i++) {
res.append(Integer.toHexString(digest[i] & 0xFF));
}
return res.toString();
}
/** Applet initialization */
public void init() {
this.subAppletClassName = getParameter("subapplet.classname");
if (subAppletClassName == null){
displayError("Init failed : Missing subapplet.classname argument");
return;
}
this.subAppletDisplayName = getParameter("subapplet.displayname");
if (subAppletDisplayName == null){
subAppletDisplayName = "Applet";
}
this.subAppletImageName = getParameter("subapplet.image");
initLoaderLayout();
validate();
String extForm = getCodeBase().toExternalForm();
String codeBase = extForm.substring(extForm.indexOf(":") + 3); // minus http:// or https://
this.installDirectory = codeBase.replace(':', '_')
.replace('.', '_').replace('/', '_').replace('~','_') // clean up the name
+ md2Hash(getParameter("archive")); // make it unique across different applet class loaders
String osName = System.getProperty("os.name");
String osArch = System.getProperty("os.arch");
if (checkOSAndArch(osName, osArch)) {
this.isInitOk = true;
} else {
displayError("Init failed : Unsupported os / arch ( " + osName + " / " + osArch + " )");
}
}
private void displayMessage(final String message){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressBar.setString(message);
}
});
}
private void displayError(final String errorMessage){
// Print message to Java console too in case it's truncated in the applet's display
System.err.println(errorMessage);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressBar.setString("Error : " + errorMessage);
}
});
}
private void setProgress(final int value) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressBar.setValue(value);
}
});
}
private void initLoaderLayout(){
setLayout(new BorderLayout());
progressBar.setBorderPainted(true);
progressBar.setStringPainted(true);
progressBar.setString("Loading...");
boolean includeImage = false;
ImageIcon image = null;
if (subAppletImageName != null){
try {
image = new ImageIcon(new URL(subAppletImageName));
includeImage = true;
} catch (MalformedURLException ex) {
ex.printStackTrace();
// not blocking
}
}
if (includeImage){
add(loaderPanel, BorderLayout.SOUTH);
loaderPanel.add(new JLabel(image), BorderLayout.CENTER);
loaderPanel.add(progressBar, BorderLayout.SOUTH);
} else {
add(loaderPanel, BorderLayout.SOUTH);
loaderPanel.add(progressBar, BorderLayout.CENTER);
}
}
/** start asynchroneous loading of libraries if needed */
public void start(){
if (isInitOk){
if (firstStart) {
firstStart = false;
String userHome = System.getProperty("user.home");
try {
// We need to load in the jogl package so that we can query the version information
ClassLoader classloader = getClass().getClassLoader();
classloader.loadClass("javax.media.opengl.GL");
Package p = Package.getPackage("javax.media.opengl");
String installDirName = userHome + File.separator + ".jogl_ext"
+ File.separator + installDirectory + File.separator + p.getImplementationVersion().replace(':', '_');
final File installDir = new File(installDirName);
Thread refresher = new Thread() {
public void run() {
refreshJOGL(installDir);
}
};
refresher.setPriority(Thread.NORM_PRIORITY - 1);
refresher.start();
}
catch (ClassNotFoundException e) {
System.err.println("Unable to load javax.media.opengl package");
System.exit(0);
}
} else if (joglStarted) {
checkNoDDrawAndUpdateDeploymentProperties();
// we have to start again the applet (start can be called multiple times,
// e.g once per tabbed browsing
subApplet.start();
}
}
}
public void stop(){
if (subApplet != null){
subApplet.stop();
}
}
public void destroy(){
if (subApplet != null){
subApplet.destroy();
}
}
/** Helper method to make it easier to call methods on the
sub-applet from JavaScript. */
public Applet getSubApplet() {
return subApplet;
}
private boolean checkOSAndArch(String osName, String osArch) {
for (int i = 0; i < allNativeLibInfo.length; i++) {
NativeLibInfo info = allNativeLibInfo[i];
if (info.matchesOSAndArch(osName, osArch)) {
nativeLibInfo = info;
return true;
}
}
return false;
}
// Get a "boolean" parameter, assuming that anything non-null aside
// from "false" is true
private boolean getBooleanParameter(String parameterName) {
String val = getParameter(parameterName);
if (val == null)
return false;
return !val.toLowerCase().equals("false");
}
private void checkNoDDrawAndUpdateDeploymentProperties() {
if (getBooleanParameter("jogl.disable.noddraw.check"))
return;
if (System.getProperty("os.name").toLowerCase().startsWith("windows") &&
!"true".equalsIgnoreCase(System.getProperty("sun.java2d.noddraw"))) {
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
updateDeploymentPropertiesImpl();
}
});
} catch (Exception e) {
}
} else {
updateDeploymentPropertiesImpl();
}
}
}
private void updateDeploymentPropertiesImpl() {
String userHome = System.getProperty("user.home");
File dontAskFile = new File(userHome + File.separator + ".jogl_ext" +
File.separator + DONT_ASK);
if (dontAskFile.exists())
return; // User asked us not to prompt again
int option = 0;
if (!getBooleanParameter("jogl.silent.noddraw.check")) {
option = JOptionPane.showOptionDialog(null,
"For best robustness of JOGL applets on Windows,\n" +
"we recommend disabling Java2D's use of DirectDraw.\n" +
"This setting will affect all applets, but is unlikely\n" +
"to slow other applets down significantly. May we update\n" +
"your deployment.properties to turn off DirectDraw for\n" +
"applets? You can change this back later if necessary\n" +
"using the Java Control Panel, Java tab, under Java\n" +
"Applet Runtime Settings.",
"Update deployment.properties?",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
new Object[] {
"Yes",
"No",
"No, Don't Ask Again"
},
"Yes");
}
if (option < 0 ||
option == 1)
return; // No
if (option == 2) {
try {
dontAskFile.createNewFile();
} catch (IOException e) {
}
return; // No, Don't Ask Again
}
try {
// Must update deployment.properties
File propsDir = new File(System.getProperty("user.home") + File.separator +
"Application Data/Sun/Java/Deployment");
if (!propsDir.exists())
// Don't know what's going on or how to set this permanently
return;
File propsFile = new File(propsDir, "deployment.properties");
if (!propsFile.exists())
// Don't know what's going on or how to set this permanently
return;
Properties props = new Properties();
InputStream input = new BufferedInputStream(new FileInputStream(propsFile));
props.load(input);
input.close();
// Search through the keys looking for JRE versions
Set/*