4a44f9bff979e6bab5d872f09ecfc0d18a10b346
[jalview.git] / getdown / src / getdown / launcher / src / main / java / com / threerings / getdown / launcher / GetdownApp.java
1 //
2 // Getdown - application installer, patcher and launcher
3 // Copyright (C) 2004-2018 Getdown authors
4 // https://github.com/threerings/getdown/blob/master/LICENSE
5
6 package com.threerings.getdown.launcher;
7
8 import java.awt.Color;
9 import java.awt.Container;
10 import java.awt.Image;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.KeyEvent;
13 import java.awt.event.WindowAdapter;
14 import java.awt.event.WindowEvent;
15 import java.io.BufferedOutputStream;
16 import java.io.File;
17 import java.io.FileOutputStream;
18 import java.io.IOException;
19 import java.io.PrintStream;
20 import java.util.ArrayList;
21 import java.util.List;
22
23 import javax.swing.AbstractAction;
24 import javax.swing.JComponent;
25 import javax.swing.JFrame;
26 import javax.swing.KeyStroke;
27 import javax.swing.WindowConstants;
28
29 import com.samskivert.swing.util.SwingUtil;
30 import com.threerings.getdown.data.Application;
31 import com.threerings.getdown.data.Build;
32 import com.threerings.getdown.data.EnvConfig;
33 import com.threerings.getdown.data.SysProps;
34 import com.threerings.getdown.util.LaunchUtil;
35 import com.threerings.getdown.util.StringUtil;
36 import static com.threerings.getdown.Log.log;
37 import jalview.bin.StartupNotificationListener;
38 import com.install4j.api.launcher.Variables;
39
40 /**
41  * The main application entry point for Getdown.
42  */
43 public class GetdownApp
44 {
45   public static String startupFilesParameterString = "";
46   /**
47    * The main entry point of the Getdown launcher application.
48    */
49   public static void main (String[] argv) {
50     try {
51       start(argv);
52     } catch (Exception e) {
53       log.warning("main() failed.", e);
54     }
55   }
56
57   /**
58    * Runs Getdown as an application, using the arguments supplie as {@code argv}.
59    * @return the {@code Getdown} instance that is running. {@link Getdown#start} will have been
60    * called on it.
61    * @throws Exception if anything goes wrong starting Getdown.
62    */
63   public static Getdown start (String[] argv) throws Exception {
64     List<EnvConfig.Note> notes = new ArrayList<>();
65     EnvConfig envc = EnvConfig.create(argv, notes);
66     if (envc == null) {
67       if (!notes.isEmpty()) for (EnvConfig.Note n : notes) System.err.println(n.message);
68       else System.err.println("Usage: java -jar getdown.jar [app_dir] [app_id] [app args]");
69       System.exit(-1);
70     }
71
72     // pipe our output into a file in the application directory
73     if (!SysProps.noLogRedir()) {
74       File logFile = new File(envc.appDir, "launcher.log");
75       try {
76         PrintStream logOut = new PrintStream(
77                 new BufferedOutputStream(new FileOutputStream(logFile)), true);
78         System.setOut(logOut);
79         System.setErr(logOut);
80       } catch (IOException ioe) {
81         log.warning("Unable to redirect output to '" + logFile + "': " + ioe);
82       }
83     }
84
85     // report any notes from reading our env config, and abort if necessary
86     boolean abort = false;
87     for (EnvConfig.Note note : notes) {
88       switch (note.level) {
89       case INFO: log.info(note.message); break;
90       case WARN: log.warning(note.message); break;
91       case ERROR: log.error(note.message); abort = true; break;
92       }
93     }
94     if (abort) System.exit(-1);
95
96     log.info("Starting ...");
97     try
98     {
99       jalview.bin.StartupNotificationListener.setListener();
100     } catch (Exception e)
101     {
102       e.printStackTrace();
103     } catch (NoClassDefFoundError e)
104     {
105       log.warning("Starting without install4j classes");
106     } catch (Throwable t)
107     {
108       t.printStackTrace();
109     }
110     
111     // record a few things for posterity
112     log.info("------------------ VM Info ------------------");
113     log.info("-- OS Name: " + System.getProperty("os.name"));
114     log.info("-- OS Arch: " + System.getProperty("os.arch"));
115     log.info("-- OS Vers: " + System.getProperty("os.version"));
116     log.info("-- Java Vers: " + System.getProperty("java.version"));
117     log.info("-- Java Home: " + System.getProperty("java.home"));
118     log.info("-- Install4j Vers: " + Variables.getCompilerVariable("sys.install4jVersion"));
119     log.info("-- Install4j Template Vers: " + System.getProperty("installer_template_version"));
120     log.info("-- User Name: " + System.getProperty("user.name"));
121     log.info("-- User Home: " + System.getProperty("user.home"));
122     log.info("-- Cur dir: " + System.getProperty("user.dir"));
123     log.info("-- Launcher version: "+Build.version());
124     log.info("-- startupFilesParameterString: " + startupFilesParameterString);
125     log.info("---------------------------------------------");
126
127     Getdown app = new Getdown(envc) {
128       @Override
129       protected Container createContainer () {
130         // create our user interface, and display it
131         if (_frame == null) {
132           _frame = new JFrame("");
133           _frame.addWindowListener(new WindowAdapter() {
134             @Override
135             public void windowClosing (WindowEvent evt) {
136               handleWindowClose();
137             }
138           });
139           
140           // keep_on_top
141           try {
142                   readConfig(false);
143           } catch (Exception e) {
144                   log.warning("Error reading config for keep_on_top");
145           }
146           // move window to top, always on top
147           if (_ifc.keepOnTop) {
148                   log.info("Keep on top set to ", "keep_on_top", _ifc.keepOnTop);
149                           _frame.setAlwaysOnTop(true);
150           }
151
152           // handle close on ESC
153           String cancelId = "Cancel"; // $NON-NLS-1$
154           _frame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
155                   KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelId);
156           _frame.getRootPane().getActionMap().put(cancelId, new AbstractAction() {
157             public void actionPerformed (ActionEvent e) {
158               handleWindowClose();
159             }
160           });
161           // this cannot be called in configureContainer as it is only allowed before the
162           // frame has been displayed for the first time
163           _frame.setUndecorated(_ifc.hideDecorations);
164           _frame.setResizable(false);
165         } else {
166           _frame.getContentPane().removeAll();
167         }
168         _frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
169         return _frame.getContentPane();
170       }
171
172       @Override
173       protected void configureContainer () {
174         if (_frame == null) return;
175
176         _frame.setTitle(_ifc.name);
177
178         try {
179           _frame.setBackground(new Color(_ifc.background, true));
180         } catch (Exception e) {
181           log.warning("Failed to set background", "bg", _ifc.background, e);
182         }
183
184         if (_ifc.iconImages != null && _ifc.iconImages.size() > 0) {
185           ArrayList<Image> icons = new ArrayList<>();
186           for (String path : _ifc.iconImages) {
187             Image img = loadImage(path);
188             if (img == null) {
189               log.warning("Error loading icon image", "path", path);
190             } else {
191               icons.add(img);
192             }
193           }
194           if (icons.isEmpty()) {
195             log.warning("Failed to load any icons", "iconImages", _ifc.iconImages);
196           } else {
197             _frame.setIconImages(icons);
198           }
199         }
200       }
201
202       @Override
203       protected void showContainer () {
204         if (_frame != null) {
205           _frame.pack();
206           SwingUtil.centerWindow(_frame);
207           _frame.setVisible(true);
208         }
209         _shownContainer = true;
210       }
211
212       @Override
213       protected void disposeContainer () {
214         if (_frame != null) {
215           _frame.dispose();
216           _frame = null;
217         }
218       }
219
220       @Override
221       protected void showDocument (String url) {
222         if (!StringUtil.couldBeValidUrl(url)) {
223           // command injection would be possible if we allowed e.g. spaces and double quotes
224           log.warning("Invalid document URL.", "url", url);
225           return;
226         }
227         String[] cmdarray;
228         if (LaunchUtil.isWindows()) {
229           String osName = System.getProperty("os.name", "");
230           if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) {
231             cmdarray = new String[] {
232                 "command.com", "/c", "start", "\"" + url + "\"" };
233           } else {
234             cmdarray = new String[] {
235                 "cmd.exe", "/c", "start", "\"\"", "\"" + url + "\"" };
236           }
237         } else if (LaunchUtil.isMacOS()) {
238           cmdarray = new String[] { "open", url };
239         } else { // Linux, Solaris, etc.
240           cmdarray = new String[] { "firefox", url };
241         }
242         try {
243           Runtime.getRuntime().exec(cmdarray);
244         } catch (Exception e) {
245           log.warning("Failed to open browser.", "cmdarray", cmdarray, e);
246         }
247       }
248
249       @Override
250       protected void exit (int exitCode) {
251         // if we're running the app in the same JVM, don't call System.exit, but do
252         // make double sure that the download window is closed.
253         if (invokeDirect()) {
254           disposeContainer();
255         } else {
256           System.exit(exitCode);
257         }
258       }
259
260       @Override
261       protected void fail (String message) {
262         super.fail(message);
263         // super.fail causes the UI to be created (if needed) on the next UI tick, so we
264         // want to wait until that happens before we attempt to redecorate the window
265         EQinvoke(new Runnable() {
266           @Override
267           public void run() {
268             // if the frame was set to be undecorated, make window decoration available
269             // to allow the user to close the window
270             if (_frame != null && _frame.isUndecorated()) {
271               _frame.dispose();
272               Color bg = _frame.getBackground();
273               if (bg != null && bg.getAlpha() < 255) {
274                 // decorated windows do not allow alpha backgrounds
275                 _frame.setBackground(
276                         new Color(bg.getRed(), bg.getGreen(), bg.getBlue()));
277               }
278               _frame.setUndecorated(false);
279               showContainer();
280             }
281           }
282         });
283       }
284
285       protected JFrame _frame;
286     };
287     
288     String startupFile = getStartupFilesParameterString();
289     if (!StringUtil.isBlank(startupFile)) {
290       Application.setStartupFilesFromParameterString(startupFile);
291     }
292  
293     app.start();
294     return app;
295   }
296   
297   public static void setStartupFilesParameterString(String parameters) {
298     startupFilesParameterString = parameters;
299   }
300   
301   public static String getStartupFilesParameterString() {
302     return startupFilesParameterString;
303   }
304 }