diff options
author | Petr Mrázek <peterix@gmail.com> | 2016-04-10 15:53:05 +0200 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2016-05-01 00:00:14 +0200 |
commit | b6d455a02bd338e9dc0faa09d4d8177ecd8d569a (patch) | |
tree | 41982bca1ede50049f2f8c7109dd18edeefde6d0 /libraries/launcher | |
parent | 47e37635f50c09b4f9a9ee7699e3120bab3e4088 (diff) | |
download | MultiMC-b6d455a02bd338e9dc0faa09d4d8177ecd8d569a.tar MultiMC-b6d455a02bd338e9dc0faa09d4d8177ecd8d569a.tar.gz MultiMC-b6d455a02bd338e9dc0faa09d4d8177ecd8d569a.tar.lz MultiMC-b6d455a02bd338e9dc0faa09d4d8177ecd8d569a.tar.xz MultiMC-b6d455a02bd338e9dc0faa09d4d8177ecd8d569a.zip |
NOISSUE reorganize and document libraries
Diffstat (limited to 'libraries/launcher')
18 files changed, 2294 insertions, 0 deletions
diff --git a/libraries/launcher/.gitignore b/libraries/launcher/.gitignore new file mode 100644 index 00000000..cc1c52bf --- /dev/null +++ b/libraries/launcher/.gitignore @@ -0,0 +1,6 @@ +.idea +*.iml +out +.classpath +.idea +.project diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt new file mode 100644 index 00000000..b62805e0 --- /dev/null +++ b/libraries/launcher/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.1) +project(launcher Java) +find_package(Java 1.6 REQUIRED COMPONENTS Development) + +include(UseJava) +set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint) +set(CMAKE_JAVA_COMPILE_FLAGS -target 1.6 -source 1.6 -Xlint:deprecation -Xlint:unchecked) + +set(SRC + # OSX things + org/simplericity/macify/eawt/Application.java + org/simplericity/macify/eawt/ApplicationAdapter.java + org/simplericity/macify/eawt/ApplicationEvent.java + org/simplericity/macify/eawt/ApplicationListener.java + org/simplericity/macify/eawt/DefaultApplication.java + + # legacy applet wrapper thing. + # The launcher has to be there for silly FML/Forge relauncher. + net/minecraft/Launcher.java + org/multimc/legacy/LegacyLauncher.java + org/multimc/LegacyFrame.java + + # onesix launcher + org/multimc/onesix/OneSixLauncher.java + + # generic launcher + org/multimc/EntryPoint.java + org/multimc/Launcher.java + org/multimc/ParseException.java + org/multimc/Utils.java + org/multimc/IconLoader.java +) +add_jar(NewLaunch ${SRC}) + diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java new file mode 100644 index 00000000..a53501ec --- /dev/null +++ b/libraries/launcher/net/minecraft/Launcher.java @@ -0,0 +1,165 @@ +/* + * Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.minecraft; + +import java.util.TreeMap; +import java.util.Map; +import java.net.URL; +import java.awt.Dimension; +import java.awt.BorderLayout; +import java.awt.Graphics; +import java.applet.Applet; +import java.applet.AppletStub; +import java.net.MalformedURLException; + +public class Launcher extends Applet implements AppletStub +{ + private Applet wrappedApplet; + private URL documentBase; + private boolean active = false; + private final Map<String, String> params; + + public Launcher(Applet applet, URL documentBase) + { + params = new TreeMap<String, String>(); + + this.setLayout(new BorderLayout()); + this.add(applet, "Center"); + this.wrappedApplet = applet; + this.documentBase = documentBase; + } + + public void setParameter(String name, String value) + { + params.put(name, value); + } + + public void replace(Applet applet) + { + this.wrappedApplet = applet; + + applet.setStub(this); + applet.setSize(getWidth(), getHeight()); + + this.setLayout(new BorderLayout()); + this.add(applet, "Center"); + + applet.init(); + active = true; + applet.start(); + validate(); + } + + @Override + public String getParameter(String name) + { + String param = params.get(name); + if (param != null) + return param; + try + { + return super.getParameter(name); + } catch (Exception ignore){} + return null; + } + + @Override + public boolean isActive() + { + return active; + } + + @Override + public void appletResize(int width, int height) + { + wrappedApplet.resize(width, height); + } + + @Override + public void resize(int width, int height) + { + wrappedApplet.resize(width, height); + } + + @Override + public void resize(Dimension d) + { + wrappedApplet.resize(d); + } + + @Override + public void init() + { + if (wrappedApplet != null) + { + wrappedApplet.init(); + } + } + + @Override + public void start() + { + wrappedApplet.start(); + active = true; + } + + @Override + public void stop() + { + wrappedApplet.stop(); + active = false; + } + + public void destroy() + { + wrappedApplet.destroy(); + } + + @Override + public URL getCodeBase() { + try { + return new URL("http://www.minecraft.net/game/"); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public URL getDocumentBase() + { + try { + return new URL("http://www.minecraft.net/game/"); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public void setVisible(boolean b) + { + super.setVisible(b); + wrappedApplet.setVisible(b); + } + public void update(Graphics paramGraphics) + { + } + public void paint(Graphics paramGraphics) + { + } +}
\ No newline at end of file diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java new file mode 100644 index 00000000..d1fc54a8 --- /dev/null +++ b/libraries/launcher/org/multimc/EntryPoint.java @@ -0,0 +1,178 @@ +package org.multimc;/* + * Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.multimc.legacy.LegacyLauncher; +import org.multimc.onesix.OneSixLauncher; +import org.simplericity.macify.eawt.Application; +import org.simplericity.macify.eawt.DefaultApplication; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.charset.Charset; + +public class EntryPoint +{ + private enum Action + { + Proceed, + Launch, + Abort + } + + public static void main(String[] args) + { + // Set the OSX application icon first, if we are on OSX. + Application application = new DefaultApplication(); + if(application.isMac()) + { + try + { + BufferedImage image = ImageIO.read(new File("icon.png")); + application.setApplicationIconImage(image); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + EntryPoint listener = new EntryPoint(); + int retCode = listener.listen(); + if (retCode != 0) + { + System.out.println("Exiting with " + retCode); + System.exit(retCode); + } + } + + private Action parseLine(String inData) throws ParseException + { + String[] pair = inData.split(" ", 2); + + if(pair.length == 1) + { + String command = pair[0]; + if (pair[0].equals("launch")) + return Action.Launch; + + else if (pair[0].equals("abort")) + return Action.Abort; + + else throw new ParseException(); + } + + if(pair.length != 2) + throw new ParseException(); + + String command = pair[0]; + String param = pair[1]; + + if(command.equals("launcher")) + { + if(param.equals("legacy")) + { + m_launcher = new LegacyLauncher(); + Utils.log("Using legacy launcher."); + Utils.log(); + return Action.Proceed; + } + if(param.equals("onesix")) + { + m_launcher = new OneSixLauncher(); + Utils.log("Using onesix launcher."); + Utils.log(); + return Action.Proceed; + } + else + throw new ParseException(); + } + + m_params.add(command, param); + //System.out.println(command + " : " + param); + return Action.Proceed; + } + + public int listen() + { + BufferedReader buffer; + try + { + buffer = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); + } catch (UnsupportedEncodingException e) + { + System.err.println("For some reason, your java does not support UTF-8. Consider living in the current century."); + e.printStackTrace(); + return 1; + } + boolean isListening = true; + boolean isAborted = false; + // Main loop + while (isListening) + { + String inData; + try + { + // Read from the pipe one line at a time + inData = buffer.readLine(); + if (inData != null) + { + Action a = parseLine(inData); + if(a == Action.Abort) + { + isListening = false; + isAborted = true; + } + if(a == Action.Launch) + { + isListening = false; + } + } + else + { + isListening = false; + isAborted = true; + } + } + catch (IOException e) + { + System.err.println("Launcher ABORT due to IO exception:"); + e.printStackTrace(); + return 1; + } + catch (ParseException e) + { + System.err.println("Launcher ABORT due to PARSE exception:"); + e.printStackTrace(); + return 1; + } + } + if(isAborted) + { + System.err.println("Launch aborted by MultiMC."); + return 1; + } + if(m_launcher != null) + { + return m_launcher.launch(m_params); + } + System.err.println("No valid launcher implementation specified."); + return 1; + } + + private ParamBucket m_params = new ParamBucket(); + private org.multimc.Launcher m_launcher; +} diff --git a/libraries/launcher/org/multimc/IconLoader.java b/libraries/launcher/org/multimc/IconLoader.java new file mode 100644 index 00000000..f1638f3a --- /dev/null +++ b/libraries/launcher/org/multimc/IconLoader.java @@ -0,0 +1,132 @@ +package org.multimc; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; + +/***************************************************************************** + * A convenience class for loading icons from images. + * + * Icons loaded from this class are formatted to fit within the required + * dimension (16x16, 32x32, or 128x128). If the source image is larger than the + * target dimension, it is shrunk down to the minimum size that will fit. If it + * is smaller, then it is only scaled up if the new scale can be a per-pixel + * linear scale (i.e., x2, x3, x4, etc). In both cases, the image's width/height + * ratio is kept the same as the source image. + * + * @author Chris Molini + *****************************************************************************/ +public class IconLoader +{ + /************************************************************************* + * Loads an icon in ByteBuffer form. + * + * @param filepath + * The location of the Image to use as an icon. + * + * @return An array of ByteBuffers containing the pixel data for the icon in + * various sizes (as recommended by the OS). + *************************************************************************/ + public static ByteBuffer[] load(String filepath) + { + BufferedImage image; + try { + image = ImageIO.read ( new File( filepath ) ); + } catch ( IOException e ) { + e.printStackTrace(); + return new ByteBuffer[0]; + } + ByteBuffer[] buffers; + buffers = new ByteBuffer[1]; + buffers[0] = loadInstance(image, 128); + return buffers; + } + + /************************************************************************* + * Copies the supplied image into a square icon at the indicated size. + * + * @param image + * The image to place onto the icon. + * @param dimension + * The desired size of the icon. + * + * @return A ByteBuffer of pixel data at the indicated size. + *************************************************************************/ + private static ByteBuffer loadInstance(BufferedImage image, int dimension) + { + BufferedImage scaledIcon = new BufferedImage(dimension, dimension, + BufferedImage.TYPE_INT_ARGB_PRE); + Graphics2D g = scaledIcon.createGraphics(); + double ratio = getIconRatio(image, scaledIcon); + double width = image.getWidth() * ratio; + double height = image.getHeight() * ratio; + g.drawImage(image, (int) ((scaledIcon.getWidth() - width) / 2), + (int) ((scaledIcon.getHeight() - height) / 2), (int) (width), + (int) (height), null); + g.dispose(); + + return convertToByteBuffer(scaledIcon); + } + + /************************************************************************* + * Gets the width/height ratio of the icon. This is meant to simplify + * scaling the icon to a new dimension. + * + * @param src + * The base image that will be placed onto the icon. + * @param icon + * The icon that will have the image placed on it. + * + * @return The amount to scale the source image to fit it onto the icon + * appropriately. + *************************************************************************/ + private static double getIconRatio(BufferedImage src, BufferedImage icon) + { + double ratio = 1; + if (src.getWidth() > icon.getWidth()) + ratio = (double) (icon.getWidth()) / src.getWidth(); + else + ratio = (int) (icon.getWidth() / src.getWidth()); + if (src.getHeight() > icon.getHeight()) + { + double r2 = (double) (icon.getHeight()) / src.getHeight(); + if (r2 < ratio) + ratio = r2; + } + else + { + double r2 = (int) (icon.getHeight() / src.getHeight()); + if (r2 < ratio) + ratio = r2; + } + return ratio; + } + + /************************************************************************* + * Converts a BufferedImage into a ByteBuffer of pixel data. + * + * @param image + * The image to convert. + * + * @return A ByteBuffer that contains the pixel data of the supplied image. + *************************************************************************/ + public static ByteBuffer convertToByteBuffer(BufferedImage image) + { + byte[] buffer = new byte[image.getWidth() * image.getHeight() * 4]; + int counter = 0; + for (int i = 0; i < image.getHeight(); i++) + for (int j = 0; j < image.getWidth(); j++) + { + int colorSpace = image.getRGB(j, i); + buffer[counter + 0] = (byte) ((colorSpace << 8) >> 24); + buffer[counter + 1] = (byte) ((colorSpace << 16) >> 24); + buffer[counter + 2] = (byte) ((colorSpace << 24) >> 24); + buffer[counter + 3] = (byte) (colorSpace >> 24); + counter += 4; + } + return ByteBuffer.wrap(buffer); + } +}
\ No newline at end of file diff --git a/libraries/launcher/org/multimc/Launcher.java b/libraries/launcher/org/multimc/Launcher.java new file mode 100644 index 00000000..1aa2b21f --- /dev/null +++ b/libraries/launcher/org/multimc/Launcher.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.multimc; + +public interface Launcher +{ + abstract int launch(ParamBucket params); +} diff --git a/libraries/launcher/org/multimc/LegacyFrame.java b/libraries/launcher/org/multimc/LegacyFrame.java new file mode 100644 index 00000000..a081f3ae --- /dev/null +++ b/libraries/launcher/org/multimc/LegacyFrame.java @@ -0,0 +1,112 @@ +package org.multimc;/* + * Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import net.minecraft.Launcher; + +import javax.imageio.ImageIO; +import java.applet.Applet; +import java.awt.*; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +public class LegacyFrame extends Frame implements WindowListener +{ + private Launcher appletWrap = null; + public LegacyFrame(String title) + { + super ( title ); + BufferedImage image; + try { + image = ImageIO.read ( new File ( "icon.png" ) ); + setIconImage ( image ); + } catch ( IOException e ) { + e.printStackTrace(); + } + this.addWindowListener ( this ); + } + + public void start ( Applet mcApplet, String user, String session, Dimension winSize, boolean maximize ) + { + try { + appletWrap = new Launcher( mcApplet, new URL ( "http://www.minecraft.net/game" ) ); + } catch ( MalformedURLException ignored ) {} + appletWrap.setParameter ( "username", user ); + appletWrap.setParameter ( "sessionid", session ); + appletWrap.setParameter ( "stand-alone", "true" ); // Show the quit button. + appletWrap.setParameter ( "demo", "false" ); + appletWrap.setParameter("fullscreen", "false"); + mcApplet.setStub(appletWrap); + this.add ( appletWrap ); + appletWrap.setPreferredSize ( winSize ); + this.pack(); + this.setLocationRelativeTo ( null ); + this.setResizable ( true ); + if ( maximize ) { + this.setExtendedState ( MAXIMIZED_BOTH ); + } + validate(); + appletWrap.init(); + appletWrap.start(); + setVisible ( true ); + } + + @Override + public void windowActivated ( WindowEvent e ) {} + + @Override + public void windowClosed ( WindowEvent e ) {} + + @Override + public void windowClosing ( WindowEvent e ) + { + new Thread() { + public void run() { + try { + Thread.sleep ( 30000L ); + } catch ( InterruptedException localInterruptedException ) { + localInterruptedException.printStackTrace(); + } + System.out.println ( "FORCING EXIT!" ); + System.exit ( 0 ); + } + } + .start(); + + if ( appletWrap != null ) { + appletWrap.stop(); + appletWrap.destroy(); + } + // old minecraft versions can hang without this >_< + System.exit ( 0 ); + } + + @Override + public void windowDeactivated ( WindowEvent e ) {} + + @Override + public void windowDeiconified ( WindowEvent e ) {} + + @Override + public void windowIconified ( WindowEvent e ) {} + + @Override + public void windowOpened ( WindowEvent e ) {} +} diff --git a/libraries/launcher/org/multimc/NotFoundException.java b/libraries/launcher/org/multimc/NotFoundException.java new file mode 100644 index 00000000..fe154a2f --- /dev/null +++ b/libraries/launcher/org/multimc/NotFoundException.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.multimc; + +public class NotFoundException extends Exception +{ +} diff --git a/libraries/launcher/org/multimc/ParamBucket.java b/libraries/launcher/org/multimc/ParamBucket.java new file mode 100644 index 00000000..2e197d9f --- /dev/null +++ b/libraries/launcher/org/multimc/ParamBucket.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.multimc; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class ParamBucket +{ + public void add(String key, String value) + { + List<String> coll = null; + if(!m_params.containsKey(key)) + { + coll = new ArrayList<String>(); + m_params.put(key, coll); + } + else + { + coll = m_params.get(key); + } + coll.add(value); + } + + public List<String> all(String key) throws NotFoundException + { + if(!m_params.containsKey(key)) + throw new NotFoundException(); + return m_params.get(key); + } + + public List<String> allSafe(String key, List<String> def) + { + if(!m_params.containsKey(key) || m_params.get(key).size() < 1) + { + return def; + } + return m_params.get(key); + } + + public List<String> allSafe(String key) + { + return allSafe(key, new ArrayList<String>()); + } + + public String first(String key) throws NotFoundException + { + List<String> list = all(key); + if(list.size() < 1) + { + throw new NotFoundException(); + } + return list.get(0); + } + + public String firstSafe(String key, String def) + { + if(!m_params.containsKey(key) || m_params.get(key).size() < 1) + { + return def; + } + return m_params.get(key).get(0); + } + + public String firstSafe(String key) + { + return firstSafe(key, ""); + } + + private HashMap<String, List<String>> m_params = new HashMap<String, List<String>>(); +} diff --git a/libraries/launcher/org/multimc/ParseException.java b/libraries/launcher/org/multimc/ParseException.java new file mode 100644 index 00000000..d9e8e53e --- /dev/null +++ b/libraries/launcher/org/multimc/ParseException.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.multimc; + +public class ParseException extends java.lang.Exception +{ + +} diff --git a/libraries/launcher/org/multimc/Utils.java b/libraries/launcher/org/multimc/Utils.java new file mode 100644 index 00000000..32cf7919 --- /dev/null +++ b/libraries/launcher/org/multimc/Utils.java @@ -0,0 +1,280 @@ +/* + * Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.multimc; + +import java.io.*; +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class Utils +{ + /** + * Combine two parts of a path. + * + * @param path1 + * @param path2 + * @return the paths, combined + */ + public static String combine(String path1, String path2) + { + File file1 = new File(path1); + File file2 = new File(file1, path2); + return file2.getPath(); + } + + /** + * Join a list of strings into a string using a separator! + * + * @param strings the string list to join + * @param separator the glue + * @return the result. + */ + public static String join(List<String> strings, String separator) + { + StringBuilder sb = new StringBuilder(); + String sep = ""; + for (String s : strings) + { + sb.append(sep).append(s); + sep = separator; + } + return sb.toString(); + } + + /** + * Adds the specified library to the classpath + * + * @param s the path to add + * @throws Exception + */ + public static void addToClassPath(String s) throws Exception + { + File f = new File(s); + URL u = f.toURI().toURL(); + URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Class urlClass = URLClassLoader.class; + Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class}); + method.setAccessible(true); + method.invoke(urlClassLoader, new Object[]{u}); + } + + /** + * Adds many libraries to the classpath + * + * @param jars the paths to add + */ + public static boolean addToClassPath(List<String> jars) + { + boolean pure = true; + // initialize the class path + for (String jar : jars) + { + try + { + Utils.addToClassPath(jar); + } catch (Exception e) + { + System.err.println("Unable to load: " + jar); + e.printStackTrace(System.err); + pure = false; + } + } + return pure; + } + + /** + * Adds the specified path to the java library path + * + * @param pathToAdd the path to add + * @throws Exception + */ + @Deprecated + public static void addLibraryPath(String pathToAdd) throws Exception + { + final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths"); + usrPathsField.setAccessible(true); + + //get array of paths + final String[] paths = (String[]) usrPathsField.get(null); + + //check if the path to add is already present + for (String path : paths) + { + if (path.equals(pathToAdd)) + { + return; + } + } + + //add the new path + final String[] newPaths = Arrays.copyOf(paths, paths.length + 1); + newPaths[newPaths.length - 1] = pathToAdd; + usrPathsField.set(null, newPaths); + } + + /** + * Finds a field that looks like a Minecraft base folder in a supplied class + * + * @param mc the class to scan + */ + public static Field getMCPathField(Class<?> mc) + { + Field[] fields = mc.getDeclaredFields(); + + for (Field f : fields) + { + if (f.getType() != File.class) + { + // Has to be File + continue; + } + if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC)) + { + // And Private Static. + continue; + } + return f; + } + return null; + } + + /** + * Log to the MultiMC console + * + * @param message A String containing the message + * @param level A String containing the level name. See MinecraftLauncher::getLevel() + */ + public static void log(String message, String level) + { + // Kinda dirty + String tag = "!![" + level + "]!"; + System.out.println(tag + message.replace("\n", "\n" + tag)); + } + + public static void log(String message) + { + log(message, "MultiMC"); + } + + public static void log() + { + System.out.println(); + } + + /** + * Pushes bytes from in to out. Closes both streams no matter what. + * @param in the input stream + * @param out the output stream + * @throws IOException + */ + private static void copyStream(InputStream in, OutputStream out) throws IOException + { + try + { + byte[] buffer = new byte[4096]; + int len; + + while((len = in.read(buffer)) >= 0) + out.write(buffer, 0, len); + } finally + { + in.close(); + out.close(); + } + } + + /** + * Replace a 'target' string 'suffix' with 'replacement' + */ + public static String replaceSuffix (String target, String suffix, String replacement) + { + if (!target.endsWith(suffix)) + { + return target; + } + String prefix = target.substring(0, target.length() - suffix.length()); + return prefix + replacement; + } + + /** + * Unzip zip file with natives 'source' into the folder 'targetFolder' + * + * Contains a hack for OSX. Yay. + * @param source + * @param targetFolder + * @throws IOException + */ + public static void unzipNatives(File source, File targetFolder) throws IOException + { + ZipFile zip = new ZipFile(source); + + boolean applyHacks = false; + String[] javaVersionElements = System.getProperty("java.version").split("[.\\-+]"); + int major = Integer.parseInt(javaVersionElements[0]); + if(major == 1) + { + major = Integer.parseInt(javaVersionElements[1]); + } + if (major >= 8) + { + applyHacks = true; + } + + try + { + Enumeration entries = zip.entries(); + + while (entries.hasMoreElements()) + { + ZipEntry entry = (ZipEntry) entries.nextElement(); + + String entryName = entry.getName(); + String fileName = entryName; + if(applyHacks) + { + fileName = replaceSuffix(entryName, ".jnilib", ".dylib"); + } + File targetFile = new File(targetFolder, fileName); + if (targetFile.getParentFile() != null) + { + targetFile.getParentFile().mkdirs(); + } + + if (entry.isDirectory()) + continue; + + copyStream(zip.getInputStream(entry), new BufferedOutputStream(new FileOutputStream(targetFile))); + } + } finally + { + zip.close(); + } + } +} + diff --git a/libraries/launcher/org/multimc/legacy/LegacyLauncher.java b/libraries/launcher/org/multimc/legacy/LegacyLauncher.java new file mode 100644 index 00000000..347bb1a2 --- /dev/null +++ b/libraries/launcher/org/multimc/legacy/LegacyLauncher.java @@ -0,0 +1,175 @@ +package org.multimc.legacy;/* + * Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.multimc.*; + +import java.applet.Applet; +import java.awt.*; +import java.io.File; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +public class LegacyLauncher implements Launcher +{ + @Override + public int launch(ParamBucket params) + { + String userName, sessionId, windowTitle, windowParams, lwjgl; + String mainClass = "net.minecraft.client.Minecraft"; + try + { + userName = params.first("userName"); + sessionId = params.first("sessionId"); + windowTitle = params.first("windowTitle"); + windowParams = params.first("windowParams"); + lwjgl = params.first("lwjgl"); + } catch (NotFoundException e) + { + System.err.println("Not enough arguments."); + return -1; + } + + String cwd = System.getProperty("user.dir"); + Dimension winSize = new Dimension(854, 480); + boolean maximize = false; + + String[] dimStrings = windowParams.split("x"); + + if (windowParams.equalsIgnoreCase("max")) + { + maximize = true; + } + else if (dimStrings.length == 2) + { + try + { + winSize = new Dimension(Integer.parseInt(dimStrings[0]), Integer.parseInt(dimStrings[1])); + } catch (NumberFormatException ignored) {} + } + + File binDir = new File(cwd, "bin"); + File lwjglDir; + if (lwjgl.equalsIgnoreCase("Mojang")) + { + lwjglDir = binDir; + } + else + { + lwjglDir = new File(lwjgl); + } + + URL[] classpath; + { + try + { + classpath = new URL[] + { + new File(binDir, "minecraft.jar").toURI().toURL(), + new File(lwjglDir, "lwjgl.jar").toURI().toURL(), + new File(lwjglDir, "lwjgl_util.jar").toURI().toURL(), + new File(lwjglDir, "jinput.jar").toURI().toURL(), + }; + } catch (MalformedURLException e) + { + System.err.println("Class path entry is badly formed:"); + e.printStackTrace(System.err); + return -1; + } + } + + String nativesDir = new File(lwjglDir, "natives").toString(); + + System.setProperty("org.lwjgl.librarypath", nativesDir); + System.setProperty("net.java.games.input.librarypath", nativesDir); + + // print the pretty things + { + Utils.log("Main Class:"); + Utils.log(" " + mainClass); + Utils.log(); + + Utils.log("Class Path:"); + for (URL s : classpath) + { + Utils.log(" " + s); + } + Utils.log(); + + Utils.log("Native Path:"); + Utils.log(" " + nativesDir); + Utils.log(); + } + + URLClassLoader cl = new URLClassLoader(classpath, LegacyLauncher.class.getClassLoader()); + + // Get the Minecraft Class and set the base folder + Class<?> mc; + try + { + mc = cl.loadClass(mainClass); + + Field f = Utils.getMCPathField(mc); + + if (f == null) + { + System.err.println("Could not find Minecraft path field. Launch failed."); + return -1; + } + + f.setAccessible(true); + f.set(null, new File(cwd)); + } catch (Exception e) + { + System.err.println("Could not set base folder. Failed to find/access Minecraft main class:"); + e.printStackTrace(System.err); + return -1; + } + + System.setProperty("minecraft.applet.TargetDirectory", cwd); + + String[] mcArgs = new String[2]; + mcArgs[0] = userName; + mcArgs[1] = sessionId; + + Utils.log("Launching with applet wrapper..."); + try + { + Class<?> MCAppletClass = cl.loadClass("net.minecraft.client.MinecraftApplet"); + Applet mcappl = (Applet) MCAppletClass.newInstance(); + LegacyFrame mcWindow = new LegacyFrame(windowTitle); + mcWindow.start(mcappl, userName, sessionId, winSize, maximize); + } catch (Exception e) + { + Utils.log("Applet wrapper failed:", "Error"); + e.printStackTrace(System.err); + Utils.log(); + Utils.log("Falling back to compatibility mode."); + try + { + mc.getMethod("main", String[].class).invoke(null, (Object) mcArgs); + } catch (Exception e1) + { + Utils.log("Failed to invoke the Minecraft main class:", "Fatal"); + e1.printStackTrace(System.err); + return -1; + } + } + + return 0; + } +} diff --git a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java new file mode 100644 index 00000000..179df0ee --- /dev/null +++ b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java @@ -0,0 +1,367 @@ +/* Copyright 2012-2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.multimc.onesix; + +import org.multimc.*; + +import java.applet.Applet; +import java.io.File; +import java.awt.*; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class OneSixLauncher implements Launcher +{ + // parameters, separated from ParamBucket + private List<String> libraries; + private List<String> extlibs; + private List<String> extlibs32; + private List<String> extlibs64; + private List<String> mcparams; + private List<String> mods; + private List<String> jarmods; + private List<String> coremods; + private List<String> traits; + private String appletClass; + private String mainClass; + private String nativePath; + private String userName, sessionId; + private String windowTitle; + private String windowParams; + + // secondary parameters + private Dimension winSize; + private boolean maximize; + private String cwd; + + // the much abused system classloader, for convenience (for further abuse) + private ClassLoader cl; + + private void processParams(ParamBucket params) throws NotFoundException + { + libraries = params.all("cp"); + extlibs = params.allSafe("ext", new ArrayList<String>()); + extlibs32 = params.allSafe("ext32", new ArrayList<String>()); + extlibs64 = params.allSafe("ext64", new ArrayList<String>()); + + // Unify the extracted native libs according to actual system architecture + String property = System.getProperty("os.arch"); + boolean is_64 = property.equalsIgnoreCase("x86_64") || property.equalsIgnoreCase("amd64"); + if(is_64) + { + extlibs.addAll(extlibs64); + } + else + { + extlibs.addAll(extlibs32); + } + + mcparams = params.allSafe("param", new ArrayList<String>() ); + mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); + appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); + mods = params.allSafe("mod", new ArrayList<String>()); + jarmods = params.allSafe("jarmod", new ArrayList<String>()); + coremods = params.allSafe("coremod", new ArrayList<String>()); + traits = params.allSafe("traits", new ArrayList<String>()); + nativePath = params.first("natives"); + + userName = params.first("userName"); + sessionId = params.first("sessionId"); + windowTitle = params.firstSafe("windowTitle", "Minecraft"); + windowParams = params.firstSafe("windowParams", "854x480"); + + cwd = System.getProperty("user.dir"); + winSize = new Dimension(854, 480); + maximize = false; + + String[] dimStrings = windowParams.split("x"); + + if (windowParams.equalsIgnoreCase("max")) + { + maximize = true; + } + else if (dimStrings.length == 2) + { + try + { + winSize = new Dimension(Integer.parseInt(dimStrings[0]), Integer.parseInt(dimStrings[1])); + } catch (NumberFormatException ignored) {} + } + } + + private void printStats() + { + Utils.log("Main Class:"); + Utils.log(" " + mainClass); + Utils.log(); + + Utils.log("Native path:"); + Utils.log(" " + nativePath); + Utils.log(); + + Utils.log("Traits:"); + Utils.log(" " + traits); + Utils.log(); + + Utils.log("Libraries:"); + for (String s : libraries) + { + File f = new File(s); + if (f.exists()) + { + Utils.log(" " + s); + } + else + { + Utils.log(" " + s + " (missing)", "Warning"); + } + } + Utils.log(); + + if(mods.size() > 0) + { + Utils.log("Mods:"); + for (String s : mods) + { + Utils.log(" " + s); + } + Utils.log(); + } + + if(coremods.size() > 0) + { + Utils.log("Core Mods:"); + for (String s : coremods) + { + Utils.log(" " + s); + } + Utils.log(); + } + + if(jarmods.size() > 0) + { + Utils.log("Jar Mods:"); + for (String s : jarmods) + { + Utils.log(" " + s); + } + Utils.log(); + } + + Utils.log("Params:"); + Utils.log(" " + mcparams.toString()); + Utils.log(); + if(maximize) + Utils.log("Window size: max (if available)"); + else + Utils.log("Window size: " + Integer.toString(winSize.width) + " x " + Integer.toString(winSize.height)); + Utils.log(); + } + + int legacyLaunch() + { + // Get the Minecraft Class and set the base folder + Class<?> mc; + try + { + mc = cl.loadClass(mainClass); + + Field f = Utils.getMCPathField(mc); + + if (f == null) + { + System.err.println("Could not find Minecraft path field."); + } + else + { + f.setAccessible(true); + f.set(null, new File(cwd)); + } + } catch (Exception e) + { + System.err.println("Could not set base folder. Failed to find/access Minecraft main class:"); + e.printStackTrace(System.err); + return -1; + } + + System.setProperty("minecraft.applet.TargetDirectory", cwd); + + String[] mcArgs = new String[2]; + mcArgs[0] = userName; + mcArgs[1] = sessionId; + + Utils.log("Launching with applet wrapper..."); + try + { + Class<?> MCAppletClass = cl.loadClass(appletClass); + Applet mcappl = (Applet) MCAppletClass.newInstance(); + LegacyFrame mcWindow = new LegacyFrame(windowTitle); + mcWindow.start(mcappl, userName, sessionId, winSize, maximize); + } catch (Exception e) + { + Utils.log("Applet wrapper failed:", "Error"); + e.printStackTrace(System.err); + Utils.log(); + Utils.log("Falling back to compatibility mode."); + try + { + mc.getMethod("main", String[].class).invoke(null, (Object) mcArgs); + } catch (Exception e1) + { + Utils.log("Failed to invoke the Minecraft main class:", "Fatal"); + e1.printStackTrace(System.err); + return -1; + } + } + return 0; + } + + int launchWithMainClass() + { + // window size, title and state, onesix + if (maximize) + { + // FIXME: there is no good way to maximize the minecraft window in onesix. + // the following often breaks linux screen setups + // mcparams.add("--fullscreen"); + } + else + { + mcparams.add("--width"); + mcparams.add(Integer.toString(winSize.width)); + mcparams.add("--height"); + mcparams.add(Integer.toString(winSize.height)); + } + + System.setProperty("minecraft.applet.TargetDirectory", cwd); + + // Get the Minecraft Class. + Class<?> mc; + try + { + mc = cl.loadClass(mainClass); + } catch (ClassNotFoundException e) + { + System.err.println("Failed to find Minecraft main class:"); + e.printStackTrace(System.err); + return -1; + } + + // get the main method. + Method meth; + try + { + meth = mc.getMethod("main", String[].class); + } catch (NoSuchMethodException e) + { + System.err.println("Failed to acquire the main method:"); + e.printStackTrace(System.err); + return -1; + } + // init params for the main method to chomp on. + String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); + try + { + // static method doesn't have an instance + meth.invoke(null, (Object) paramsArray); + } catch (Exception e) + { + System.err.println("Failed to start Minecraft:"); + e.printStackTrace(System.err); + return -1; + } + return 0; + } + + @Override + public int launch(ParamBucket params) + { + // get and process the launch script params + try + { + processParams(params); + } catch (NotFoundException e) + { + System.err.println("Not enough arguments."); + e.printStackTrace(System.err); + return -1; + } + + // add libraries to classpath + if(!Utils.addToClassPath(libraries)) + { + System.err.println("Halting launch due to previous errors."); + return -1; + } + + // print the pretty things + printStats(); + + // extract native libs (depending on platform here... java!) + Utils.log("Preparing native libraries..."); + for(String extlib: extlibs) + { + try + { + File extlibf = new File(extlib); + Utils.log("Extracting " + extlibf.getName()); + Utils.unzipNatives(extlibf, new File(nativePath)); + } catch (IOException e) + { + System.err.println("Failed to extract native library:"); + e.printStackTrace(System.err); + return -1; + } + } + Utils.log(); + + // set the native libs path... the brute force way + try + { + System.setProperty("java.library.path", nativePath); + System.setProperty("org.lwjgl.librarypath", nativePath); + System.setProperty("net.java.games.input.librarypath", nativePath); + // by the power of reflection, initialize native libs again. DIRTY! + // this is SO BAD. imagine doing that to ld + Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); + fieldSysPath.setAccessible( true ); + fieldSysPath.set( null, null ); + } + catch (Exception e) + { + System.err.println("Failed to set the native library path:"); + e.printStackTrace(System.err); + System.err.println("Minecraft might fail to launch..."); + } + + // grab the system classloader and ... + cl = ClassLoader.getSystemClassLoader(); + + if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") ) + { + // legacy launch uses the applet wrapper + return legacyLaunch(); + } + else + { + // normal launch just calls main() + return launchWithMainClass(); + } + } +} diff --git a/libraries/launcher/org/simplericity/macify/eawt/Application.java b/libraries/launcher/org/simplericity/macify/eawt/Application.java new file mode 100644 index 00000000..153bb9ee --- /dev/null +++ b/libraries/launcher/org/simplericity/macify/eawt/Application.java @@ -0,0 +1,176 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.awt.*; +import java.awt.image.BufferedImage; + +/** + * The Macify Library API interface provides integration with the OS X platform for Java Applications. + * The API includes a facade to the + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/index.html"> + * Apple Java Extensions API + * </a>. + * Additionally, it provides access to several useful methods in the Cocoa NSApplication API. + * + * The default implementation of this interface is {@link org.simplericity.macify.eawt.DefaultApplication}. + */ +public interface Application { + + static int REQUEST_USER_ATTENTION_TYPE_CRITICAL = 1 ; + static int REQUEST_USER_ATTENTION_TYPE_INFORMATIONAL = 2 ; + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#addAboutMenuItem()"> + * Apple's API + * </a>. + */ + void addAboutMenuItem(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#addApplicationListener(com.apple.eawt.ApplicationListener)"> + * Apple's API + * </a>. + */ + void addApplicationListener(ApplicationListener applicationListener); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#addPreferencesMenuItem()"> + * Apple's API + * </a>. + */ + void addPreferencesMenuItem(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledAboutMenu()"> + * Apple's API + * </a>. + */ + boolean getEnabledAboutMenu(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledPreferencesMenu()"> + * Apple's API + * </a>. + */ + boolean getEnabledPreferencesMenu(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#isAboutMenuItemPresent()"> + * Apple's API + * </a>. + */ + boolean isAboutMenuItemPresent(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#isPreferencesMenuItemPresent()"> + * Apple's API + * </a>. + */ + boolean isPreferencesMenuItemPresent(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#removeAboutMenuItem()"> + * Apple's API + * </a>. + */ + void removeAboutMenuItem(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#removeApplicationListener(com.apple.eawt.ApplicationListener)"> + * Apple's API + * </a>. + */ + void removeApplicationListener(ApplicationListener applicationListener); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#removePreferencesMenuItem()"> + * Apple's API + * </a>. + */ + void removePreferencesMenuItem(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledAboutMenu()"> + * Apple's API + * </a>. + */ + void setEnabledAboutMenu(boolean enabled); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledPreferencesMenu()"> + * Apple's API + * </a>. + */ + void setEnabledPreferencesMenu(boolean enabled); + + /** + * See + * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getMouseLocationOnScreen()"> + * Apple's API + * </a>. + */ + Point getMouseLocationOnScreen(); + + /** + * See + * <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/doc/uid/TP40004004"> + * Apple's NSApplication Class Reference + * </a>. + * @param type on of {@link #REQUEST_USER_ATTENTION_TYPE_CRITICAL} or {@link #REQUEST_USER_ATTENTION_TYPE_INFORMATIONAL}. + */ + int requestUserAttention(int type); + + /** + * See + * <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/doc/uid/TP40004004"> + * Apple's NSApplication Class Reference + * </a> + */ + void cancelUserAttentionRequest(int request); + + /** + * Update the application's icon image + * @param image + */ + void setApplicationIconImage(BufferedImage image); + + /** + * Get the application's icon image. + */ + BufferedImage getApplicationIconImage(); + + /** + * Determines whether the application is running on a Mac AND the Apple Extensions API classes are available. + * @return + */ + boolean isMac(); + + +} diff --git a/libraries/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java b/libraries/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java new file mode 100644 index 00000000..e9c3db7d --- /dev/null +++ b/libraries/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java @@ -0,0 +1,48 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class ApplicationAdapter implements ApplicationListener { + + public void handleQuit(ApplicationEvent event) { + + } + + public void handleAbout(ApplicationEvent event) { + + } + + public void handleOpenApplication(ApplicationEvent event) { + + } + + public void handleOpenFile(ApplicationEvent event) { + + } + + public void handlePreferences(ApplicationEvent event) { + + } + + public void handlePrintFile(ApplicationEvent event) { + + } + + public void handleReOpenApplication(ApplicationEvent event) { + + } +} diff --git a/libraries/launcher/org/simplericity/macify/eawt/ApplicationEvent.java b/libraries/launcher/org/simplericity/macify/eawt/ApplicationEvent.java new file mode 100644 index 00000000..78420355 --- /dev/null +++ b/libraries/launcher/org/simplericity/macify/eawt/ApplicationEvent.java @@ -0,0 +1,25 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public interface ApplicationEvent { + String getFilename(); + boolean isHandled(); + void setHandled(boolean handled); + Object getSource(); + String toString(); +} diff --git a/libraries/launcher/org/simplericity/macify/eawt/ApplicationListener.java b/libraries/launcher/org/simplericity/macify/eawt/ApplicationListener.java new file mode 100644 index 00000000..a291bee4 --- /dev/null +++ b/libraries/launcher/org/simplericity/macify/eawt/ApplicationListener.java @@ -0,0 +1,27 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public interface ApplicationListener { + void handleAbout(ApplicationEvent event); + void handleOpenApplication(ApplicationEvent event); + void handleOpenFile(ApplicationEvent event); + void handlePreferences(ApplicationEvent event); + void handlePrintFile(ApplicationEvent event); + void handleQuit(ApplicationEvent event); + void handleReOpenApplication(ApplicationEvent event); +} diff --git a/libraries/launcher/org/simplericity/macify/eawt/DefaultApplication.java b/libraries/launcher/org/simplericity/macify/eawt/DefaultApplication.java new file mode 100644 index 00000000..5752a350 --- /dev/null +++ b/libraries/launcher/org/simplericity/macify/eawt/DefaultApplication.java @@ -0,0 +1,418 @@ +package org.simplericity.macify.eawt; + +/* + * Copyright 2007 Eirik Bjorsnos. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.lang.reflect.*; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +/** + * Implements Application by calling the Mac OS X API through reflection. + * If this class is used on a non-OS X platform the operations will have no effect or they will simulate + * what the Apple API would do for those who manipulate state. ({@link #setEnabledAboutMenu(boolean)} etc.) + */ +@SuppressWarnings("unchecked") +public class DefaultApplication implements Application { + + private Object application; + private Class applicationListenerClass; + + Map listenerMap = Collections.synchronizedMap(new HashMap<Object, Object>()); + private boolean enabledAboutMenu = true; + private boolean enabledPreferencesMenu; + private boolean aboutMenuItemPresent = true; + private boolean preferencesMenuItemPresent; + private ClassLoader classLoader; + + public DefaultApplication() { + try { + final File file = new File("/System/Library/Java"); + if (file.exists()) { + ClassLoader scl = ClassLoader.getSystemClassLoader(); + Class clc = scl.getClass(); + if (URLClassLoader.class.isAssignableFrom(clc)) { + Method addUrl = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class}); + addUrl.setAccessible(true); + addUrl.invoke(scl, new Object[]{file.toURI().toURL()}); + } + } + + Class appClass = Class.forName("com.apple.eawt.Application"); + application = appClass.getMethod("getApplication", new Class[0]).invoke(null, new Object[0]); + applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener"); + } catch (ClassNotFoundException e) { + application = null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + + } + + public boolean isMac() { + return application != null; + } + + public void addAboutMenuItem() { + if (isMac()) { + callMethod(application, "addAboutMenuItem"); + } else { + this.aboutMenuItemPresent = true; + } + } + + public void addApplicationListener(ApplicationListener applicationListener) { + + if (!Modifier.isPublic(applicationListener.getClass().getModifiers())) { + throw new IllegalArgumentException("ApplicationListener must be a public class"); + } + if (isMac()) { + Object listener = Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{applicationListenerClass}, + new ApplicationListenerInvocationHandler(applicationListener)); + + callMethod(application, "addApplicationListener", new Class[]{applicationListenerClass}, new Object[]{listener}); + listenerMap.put(applicationListener, listener); + } else { + listenerMap.put(applicationListener, applicationListener); + } + } + + public void addPreferencesMenuItem() { + if (isMac()) { + callMethod("addPreferencesMenuItem"); + } else { + this.preferencesMenuItemPresent = true; + } + } + + public boolean getEnabledAboutMenu() { + if (isMac()) { + return callMethod("getEnabledAboutMenu").equals(Boolean.TRUE); + } else { + return enabledAboutMenu; + } + } + + public boolean getEnabledPreferencesMenu() { + if (isMac()) { + Object result = callMethod("getEnabledPreferencesMenu"); + return result.equals(Boolean.TRUE); + } else { + return enabledPreferencesMenu; + } + } + + public Point getMouseLocationOnScreen() { + if (isMac()) { + try { + Method method = application.getClass().getMethod("getMouseLocationOnScreen", new Class[0]); + return (Point) method.invoke(null, new Object[0]); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + return new Point(0, 0); + } + } + + public boolean isAboutMenuItemPresent() { + if (isMac()) { + return callMethod("isAboutMenuItemPresent").equals(Boolean.TRUE); + } else { + return aboutMenuItemPresent; + } + } + + public boolean isPreferencesMenuItemPresent() { + if (isMac()) { + return callMethod("isPreferencesMenuItemPresent").equals(Boolean.TRUE); + } else { + return this.preferencesMenuItemPresent; + } + } + + public void removeAboutMenuItem() { + if (isMac()) { + callMethod("removeAboutMenuItem"); + } else { + this.aboutMenuItemPresent = false; + } + } + + public synchronized void removeApplicationListener(ApplicationListener applicationListener) { + if (isMac()) { + Object listener = listenerMap.get(applicationListener); + callMethod(application, "removeApplicationListener", new Class[]{applicationListenerClass}, new Object[]{listener}); + + } + listenerMap.remove(applicationListener); + } + + public void removePreferencesMenuItem() { + if (isMac()) { + callMethod("removeAboutMenuItem"); + } else { + this.preferencesMenuItemPresent = false; + } + } + + public void setEnabledAboutMenu(boolean enabled) { + if (isMac()) { + callMethod(application, "setEnabledAboutMenu", new Class[]{Boolean.TYPE}, new Object[]{Boolean.valueOf(enabled)}); + } else { + this.enabledAboutMenu = enabled; + } + } + + public void setEnabledPreferencesMenu(boolean enabled) { + if (isMac()) { + callMethod(application, "setEnabledPreferencesMenu", new Class[]{Boolean.TYPE}, new Object[]{Boolean.valueOf(enabled)}); + } else { + this.enabledPreferencesMenu = enabled; + } + + } + + public int requestUserAttention(int type) { + if (type != REQUEST_USER_ATTENTION_TYPE_CRITICAL && type != REQUEST_USER_ATTENTION_TYPE_INFORMATIONAL) { + throw new IllegalArgumentException("Requested user attention type is not allowed: " + type); + } + try { + Object application = getNSApplication(); + Field critical = application.getClass().getField("UserAttentionRequestCritical"); + Field informational = application.getClass().getField("UserAttentionRequestInformational"); + Field actual = type == REQUEST_USER_ATTENTION_TYPE_CRITICAL ? critical : informational; + + return ((Integer) application.getClass().getMethod("requestUserAttention", new Class[]{Integer.TYPE}).invoke(application, new Object[]{actual.get(null)})).intValue(); + + } catch (ClassNotFoundException e) { + return -1; + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + public void cancelUserAttentionRequest(int request) { + try { + Object application = getNSApplication(); + application.getClass().getMethod("cancelUserAttentionRequest", new Class[]{Integer.TYPE}).invoke(application, new Object[]{new Integer(request)}); + } catch (ClassNotFoundException e) { + // Nada + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private Object getNSApplication() throws ClassNotFoundException { + try { + Class applicationClass = Class.forName("com.apple.cocoa.application.NSApplication"); + return applicationClass.getMethod("sharedApplication", new Class[0]).invoke(null, new Object[0]); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + public void setApplicationIconImage(BufferedImage image) { + if (isMac()) { + try { + Method setDockIconImage = application.getClass().getMethod("setDockIconImage", Image.class); + + try { + setDockIconImage.invoke(application, image); + } catch (IllegalAccessException e) { + + } catch (InvocationTargetException e) { + + } + } catch (NoSuchMethodException mnfe) { + + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + ImageIO.write(image, "png", stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try { + Class nsDataClass = Class.forName("com.apple.cocoa.foundation.NSData"); + Constructor constructor = nsDataClass.getConstructor(new Class[]{new byte[0].getClass()}); + + Object nsData = constructor.newInstance(new Object[]{stream.toByteArray()}); + + Class nsImageClass = Class.forName("com.apple.cocoa.application.NSImage"); + Object nsImage = nsImageClass.getConstructor(new Class[]{nsDataClass}).newInstance(new Object[]{nsData}); + + Object application = getNSApplication(); + + application.getClass().getMethod("setApplicationIconImage", new Class[]{nsImageClass}).invoke(application, new Object[]{nsImage}); + + } catch (ClassNotFoundException e) { + + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + + } + + } + } + + public BufferedImage getApplicationIconImage() { + if (isMac()) { + + try { + Method getDockIconImage = application.getClass().getMethod("getDockIconImage"); + try { + return (BufferedImage) getDockIconImage.invoke(application); + } catch (IllegalAccessException e) { + + } catch (InvocationTargetException e) { + + } + } catch (NoSuchMethodException nsme) { + + try { + Class nsDataClass = Class.forName("com.apple.cocoa.foundation.NSData"); + Class nsImageClass = Class.forName("com.apple.cocoa.application.NSImage"); + Object application = getNSApplication(); + Object nsImage = application.getClass().getMethod("applicationIconImage", new Class[0]).invoke(application, new Object[0]); + + Object nsData = nsImageClass.getMethod("TIFFRepresentation", new Class[0]).invoke(nsImage, new Object[0]); + + Integer length = (Integer) nsDataClass.getMethod("length", new Class[0]).invoke(nsData, new Object[0]); + byte[] bytes = (byte[]) nsDataClass.getMethod("bytes", new Class[]{Integer.TYPE, Integer.TYPE}).invoke(nsData, new Object[]{Integer.valueOf(0), length}); + + BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes)); + return image; + + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + } + + return null; + } + + private Object callMethod(String methodname) { + return callMethod(application, methodname, new Class[0], new Object[0]); + } + + private Object callMethod(Object object, String methodname) { + return callMethod(object, methodname, new Class[0], new Object[0]); + } + + private Object callMethod(Object object, String methodname, Class[] classes, Object[] arguments) { + try { + if (classes == null) { + classes = new Class[arguments.length]; + for (int i = 0; i < classes.length; i++) { + classes[i] = arguments[i].getClass(); + + } + } + Method addListnerMethod = object.getClass().getMethod(methodname, classes); + return addListnerMethod.invoke(object, arguments); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + class ApplicationListenerInvocationHandler implements InvocationHandler { + private ApplicationListener applicationListener; + + ApplicationListenerInvocationHandler(ApplicationListener applicationListener) { + this.applicationListener = applicationListener; + } + + public Object invoke(Object object, Method appleMethod, Object[] objects) throws Throwable { + + ApplicationEvent event = createApplicationEvent(objects[0]); + try { + Method method = applicationListener.getClass().getMethod(appleMethod.getName(), new Class[]{ApplicationEvent.class}); + return method.invoke(applicationListener, new Object[]{event}); + } catch (NoSuchMethodException e) { + if (appleMethod.getName().equals("equals") && objects.length == 1) { + return Boolean.valueOf(object == objects[0]); + } + return null; + } + } + } + + private ApplicationEvent createApplicationEvent(final Object appleApplicationEvent) { + return (ApplicationEvent) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ApplicationEvent.class}, new InvocationHandler() { + public Object invoke(Object o, Method method, Object[] objects) throws Throwable { + return appleApplicationEvent.getClass().getMethod(method.getName(), method.getParameterTypes()).invoke(appleApplicationEvent, objects); + } + }); + } +} |