Merge branch 'task/JAL-3236_install4j_linux_unix_userland_installers' into develop
[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.EventQueue;
11 import java.awt.Image;
12 import java.awt.event.ActionEvent;
13 import java.awt.event.KeyEvent;
14 import java.awt.event.WindowAdapter;
15 import java.awt.event.WindowEvent;
16 import java.io.BufferedOutputStream;
17 import java.io.File;
18 import java.io.FileOutputStream;
19 import java.io.IOException;
20 import java.io.PrintStream;
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import javax.swing.AbstractAction;
25 import javax.swing.JComponent;
26 import javax.swing.JFrame;
27 import javax.swing.KeyStroke;
28 import javax.swing.WindowConstants;
29
30 import com.install4j.api.launcher.StartupNotification;
31 import com.samskivert.swing.util.SwingUtil;
32 import com.threerings.getdown.data.Application;
33 import com.threerings.getdown.data.EnvConfig;
34 import com.threerings.getdown.data.SysProps;
35 import com.threerings.getdown.util.LaunchUtil;
36 import com.threerings.getdown.util.StringUtil;
37 import static com.threerings.getdown.Log.log;
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     try
96     {
97       StartupNotification.registerStartupListener(
98               new StartupNotification.Listener() {
99                 @Override
100                 public void startupPerformed(String parameters)
101                 { 
102                   log.warning("startupPerformed: '"+parameters+"'");
103                   setStartupFilesParameterString(parameters);
104                 }
105               });
106     } catch (Exception e)
107     {
108       e.printStackTrace();
109     }
110     
111     //Thread.sleep(200);
112
113     // record a few things for posterity
114     log.info("------------------ VM Info ------------------");
115     log.info("-- OS Name: " + System.getProperty("os.name"));
116     log.info("-- OS Arch: " + System.getProperty("os.arch"));
117     log.info("-- OS Vers: " + System.getProperty("os.version"));
118     log.info("-- Java Vers: " + System.getProperty("java.version"));
119     log.info("-- Java Home: " + System.getProperty("java.home"));
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("-- startupFilesParameterString: " + startupFilesParameterString);
124     log.info("---------------------------------------------");
125
126     Getdown app = new Getdown(envc) {
127       @Override
128       protected Container createContainer () {
129         // create our user interface, and display it
130         if (_frame == null) {
131           _frame = new JFrame("");
132           _frame.addWindowListener(new WindowAdapter() {
133             @Override
134             public void windowClosing (WindowEvent evt) {
135               handleWindowClose();
136             }
137           });
138           // handle close on ESC
139           String cancelId = "Cancel"; // $NON-NLS-1$
140           _frame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
141                   KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelId);
142           _frame.getRootPane().getActionMap().put(cancelId, new AbstractAction() {
143             public void actionPerformed (ActionEvent e) {
144               handleWindowClose();
145             }
146           });
147           // this cannot be called in configureContainer as it is only allowed before the
148           // frame has been displayed for the first time
149           _frame.setUndecorated(_ifc.hideDecorations);
150           _frame.setResizable(false);
151         } else {
152           _frame.getContentPane().removeAll();
153         }
154         _frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
155         return _frame.getContentPane();
156       }
157
158       @Override
159       protected void configureContainer () {
160         if (_frame == null) return;
161
162         _frame.setTitle(_ifc.name);
163
164         try {
165           _frame.setBackground(new Color(_ifc.background, true));
166         } catch (Exception e) {
167           log.warning("Failed to set background", "bg", _ifc.background, e);
168         }
169
170         if (_ifc.iconImages != null) {
171           ArrayList<Image> icons = new ArrayList<>();
172           for (String path : _ifc.iconImages) {
173             Image img = loadImage(path);
174             if (img == null) {
175               log.warning("Error loading icon image", "path", path);
176             } else {
177               icons.add(img);
178             }
179           }
180           if (icons.isEmpty()) {
181             log.warning("Failed to load any icons", "iconImages", _ifc.iconImages);
182           } else {
183             _frame.setIconImages(icons);
184           }
185         }
186       }
187
188       @Override
189       protected void showContainer () {
190         if (_frame != null) {
191           _frame.pack();
192           SwingUtil.centerWindow(_frame);
193           _frame.setVisible(true);
194         }
195       }
196
197       @Override
198       protected void disposeContainer () {
199         if (_frame != null) {
200           _frame.dispose();
201           _frame = null;
202         }
203       }
204
205       @Override
206       protected void showDocument (String url) {
207         if (!StringUtil.couldBeValidUrl(url)) {
208           // command injection would be possible if we allowed e.g. spaces and double quotes
209           log.warning("Invalid document URL.", "url", url);
210           return;
211         }
212         String[] cmdarray;
213         if (LaunchUtil.isWindows()) {
214           String osName = System.getProperty("os.name", "");
215           if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) {
216             cmdarray = new String[] {
217                 "command.com", "/c", "start", "\"" + url + "\"" };
218           } else {
219             cmdarray = new String[] {
220                 "cmd.exe", "/c", "start", "\"\"", "\"" + url + "\"" };
221           }
222         } else if (LaunchUtil.isMacOS()) {
223           cmdarray = new String[] { "open", url };
224         } else { // Linux, Solaris, etc.
225           cmdarray = new String[] { "firefox", url };
226         }
227         try {
228           Runtime.getRuntime().exec(cmdarray);
229         } catch (Exception e) {
230           log.warning("Failed to open browser.", "cmdarray", cmdarray, e);
231         }
232       }
233
234       @Override
235       protected void exit (int exitCode) {
236         // if we're running the app in the same JVM, don't call System.exit, but do
237         // make double sure that the download window is closed.
238         if (invokeDirect()) {
239           disposeContainer();
240         } else {
241           System.exit(exitCode);
242         }
243       }
244
245       @Override
246       protected void fail (String message) {
247         super.fail(message);
248         // super.fail causes the UI to be created (if needed) on the next UI tick, so we
249         // want to wait until that happens before we attempt to redecorate the window
250         EventQueue.invokeLater(new Runnable() {
251           @Override
252           public void run() {
253             // if the frame was set to be undecorated, make window decoration available
254             // to allow the user to close the window
255             if (_frame != null && _frame.isUndecorated()) {
256               _frame.dispose();
257               Color bg = _frame.getBackground();
258               if (bg != null && bg.getAlpha() < 255) {
259                 // decorated windows do not allow alpha backgrounds
260                 _frame.setBackground(
261                         new Color(bg.getRed(), bg.getGreen(), bg.getBlue()));
262               }
263               _frame.setUndecorated(false);
264               showContainer();
265             }
266           }
267         });
268       }
269
270       protected JFrame _frame;
271     };
272     /*
273     log.warning("Startup file?",
274             "paramstring", '"'+getStartupFilesParameterString()+'"',
275             "isWindows", LaunchUtil.isWindows(),
276             "argv.length", argv.length,
277             "argv[0]", argv.length>0?argv[0]:"NULL",
278             "argv[1]", argv.length>1?argv[1]:"NULL",
279             "argv[2]", argv.length>2?argv[2]:"NULL",
280             "argv[3]", argv.length>3?argv[3]:"NULL"
281               );
282               */
283     if (getStartupFilesParameterString() != null && getStartupFilesParameterString().length() > 0) {
284       app.setStartupFilesFromParameterString(getStartupFilesParameterString());
285     } else if (
286             getStartupFilesParameterString().length() == 0
287             && LaunchUtil.isWindows()
288             && argv.length >= 3
289             && argv[0].equals(".")
290             && argv[1].equals("noappid")
291             && argv[2].endsWith("."+Application.locatorFileExtension)
292             ) {
293       log.info("Jalview Version Locator in args: "+argv[2]);
294       app.setStartupFilesFromParameterString(argv[2]);
295       String[] newArgv = new String[argv.length - 1];
296       System.arraycopy(argv, 0, newArgv, 0, 2);
297       System.arraycopy(argv, 3, newArgv, 2, argv.length - 3);
298       argv = newArgv;
299     }
300     app.start();
301     return app;
302   }
303   
304   public static void setStartupFilesParameterString(String parameters) {
305     startupFilesParameterString = parameters;
306   }
307   
308   public static String getStartupFilesParameterString() {
309     return startupFilesParameterString;
310   }
311 }