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