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