Make invirt-update-vnc-cert sign the applet if a signing keystore is configured and...
[invirt/packages/invirt-vnc-client.git] / VncViewer.java
1 //
2 //  Copyright (C) 2001-2004 HorizonLive.com, Inc.  All Rights Reserved.
3 //  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
4 //  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
5 //
6 //  This is free software; you can redistribute it and/or modify
7 //  it under the terms of the GNU General Public License as published by
8 //  the Free Software Foundation; either version 2 of the License, or
9 //  (at your option) any later version.
10 //
11 //  This software is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this software; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
19 //  USA.
20 //
21
22 //
23 // VncViewer.java - the VNC viewer applet.  This class mainly just sets up the
24 // user interface, leaving it to the VncCanvas to do the actual rendering of
25 // a VNC desktop.
26 //
27
28 import java.awt.*;
29 import java.awt.event.*;
30 import java.io.*;
31 import java.net.*;
32
33 public class VncViewer extends java.applet.Applet
34   implements java.lang.Runnable, WindowListener {
35
36   boolean inAnApplet = true;
37   boolean inSeparateFrame = false;
38
39   //
40   // main() is called when run as a java program from the command line.
41   // It simply runs the applet inside a newly-created frame.
42   //
43
44   public static void main(String[] argv) {
45     VncViewer v = new VncViewer();
46     v.mainArgs = argv;
47     v.inAnApplet = false;
48     v.inSeparateFrame = true;
49
50     v.init();
51     v.start();
52   }
53
54   String[] mainArgs;
55
56   RfbProto rfb;
57   Thread rfbThread;
58
59   Frame vncFrame;
60   Container vncContainer;
61   ScrollPane desktopScrollPane;
62   GridBagLayout gridbag;
63   ButtonPanel buttonPanel;
64   Label connStatusLabel;
65   VncCanvas vc;
66   OptionsFrame options;
67   ClipboardFrame clipboard;
68   RecordingFrame rec;
69
70   // Control session recording.
71   Object recordingSync;
72   String sessionFileName;
73   boolean recordingActive;
74   boolean recordingStatusChanged;
75   String cursorUpdatesDef;
76   String eightBitColorsDef;
77
78   // Variables read from parameter values.
79   String socketFactory;
80   String host;
81   int port;
82   String passwordParam;
83   boolean showControls;
84   boolean offerRelogin;
85   boolean showOfflineDesktop;
86   int deferScreenUpdates;
87   int deferCursorUpdates;
88   int deferUpdateRequests;
89
90   // Reference to this applet for inter-applet communication.
91   public static java.applet.Applet refApplet;
92
93   //
94   // init()
95   //
96
97   public void init() {
98
99     readParameters();
100
101     refApplet = this;
102
103     if (inSeparateFrame) {
104       vncFrame = new Frame("TightVNC");
105       if (!inAnApplet) {
106         vncFrame.add("Center", this);
107       }
108       vncContainer = vncFrame;
109     } else {
110       vncContainer = this;
111     }
112
113     recordingSync = new Object();
114
115     options = new OptionsFrame(this);
116     clipboard = new ClipboardFrame(this);
117     if (RecordingFrame.checkSecurity())
118       rec = new RecordingFrame(this);
119
120     sessionFileName = null;
121     recordingActive = false;
122     recordingStatusChanged = false;
123     cursorUpdatesDef = null;
124     eightBitColorsDef = null;
125
126     if (inSeparateFrame)
127       vncFrame.addWindowListener(this);
128
129     rfbThread = new Thread(this);
130     rfbThread.start();
131   }
132
133   public void update(Graphics g) {
134   }
135
136   //
137   // run() - executed by the rfbThread to deal with the RFB socket.
138   //
139
140   public void run() {
141
142     gridbag = new GridBagLayout();
143     vncContainer.setLayout(gridbag);
144
145     GridBagConstraints gbc = new GridBagConstraints();
146     gbc.gridwidth = GridBagConstraints.REMAINDER;
147     gbc.anchor = GridBagConstraints.NORTHWEST;
148
149     if (showControls) {
150       buttonPanel = new ButtonPanel(this);
151       gridbag.setConstraints(buttonPanel, gbc);
152       vncContainer.add(buttonPanel);
153     }
154
155     try {
156       connectAndAuthenticate();
157       doProtocolInitialisation();
158
159       // FIXME: Use auto-scaling not only in a separate frame.
160       if (options.autoScale && inSeparateFrame) {
161         Dimension screenSize;
162         try {
163           screenSize = vncContainer.getToolkit().getScreenSize();
164         } catch (Exception e) {
165           screenSize = new Dimension(0, 0);
166         }
167         createCanvas(screenSize.width - 32, screenSize.height - 32);
168       } else {
169         createCanvas(0, 0);
170       }
171
172       gbc.weightx = 1.0;
173       gbc.weighty = 1.0;
174
175       if (inSeparateFrame) {
176
177         // Create a panel which itself is resizeable and can hold
178         // non-resizeable VncCanvas component at the top left corner.
179         Panel canvasPanel = new Panel();
180         canvasPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
181         canvasPanel.add(vc);
182
183         // Create a ScrollPane which will hold a panel with VncCanvas
184         // inside.
185         desktopScrollPane = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
186         gbc.fill = GridBagConstraints.BOTH;
187         gridbag.setConstraints(desktopScrollPane, gbc);
188         desktopScrollPane.add(canvasPanel);
189
190         // Finally, add our ScrollPane to the Frame window.
191         vncFrame.add(desktopScrollPane);
192         vncFrame.setTitle(rfb.desktopName);
193         vncFrame.pack();
194         vc.resizeDesktopFrame();
195
196       } else {
197
198         // Just add the VncCanvas component to the Applet.
199         gridbag.setConstraints(vc, gbc);
200         add(vc);
201         validate();
202
203       }
204
205       if (showControls)
206         buttonPanel.enableButtons();
207
208       moveFocusToDesktop();
209       processNormalProtocol();
210
211     } catch (NoRouteToHostException e) {
212       fatalError("Network error: no route to server: " + host, e);
213     } catch (UnknownHostException e) {
214       fatalError("Network error: server name unknown: " + host, e);
215     } catch (ConnectException e) {
216       fatalError("Network error: could not connect to server: " +
217                  host + ":" + port, e);
218     } catch (EOFException e) {
219       if (showOfflineDesktop) {
220         e.printStackTrace();
221         System.out.println("Network error: remote side closed connection");
222         if (vc != null) {
223           vc.enableInput(false);
224         }
225         if (inSeparateFrame) {
226           vncFrame.setTitle(rfb.desktopName + " [disconnected]");
227         }
228         if (rfb != null && !rfb.closed())
229           rfb.close();
230         if (showControls && buttonPanel != null) {
231           buttonPanel.disableButtonsOnDisconnect();
232           if (inSeparateFrame) {
233             vncFrame.pack();
234           } else {
235             validate();
236           }
237         }
238       } else {
239         fatalError("Network error: remote side closed connection", e);
240       }
241     } catch (IOException e) {
242       String str = e.getMessage();
243       if (str != null && str.length() != 0) {
244         fatalError("Network Error: " + str, e);
245       } else {
246         fatalError(e.toString(), e);
247       }
248     } catch (Exception e) {
249       String str = e.getMessage();
250       if (str != null && str.length() != 0) {
251         fatalError("Error: " + str, e);
252       } else {
253         fatalError(e.toString(), e);
254       }
255     }
256     
257   }
258
259   //
260   // Create a VncCanvas instance.
261   //
262
263   void createCanvas(int maxWidth, int maxHeight) throws IOException {
264     // Determine if Java 2D API is available and use a special
265     // version of VncCanvas if it is present.
266     vc = null;
267     try {
268       // This throws ClassNotFoundException if there is no Java 2D API.
269       Class cl = Class.forName("java.awt.Graphics2D");
270       // If we could load Graphics2D class, then we can use VncCanvas2D.
271       cl = Class.forName("VncCanvas2");
272       Class[] argClasses = { this.getClass(), Integer.TYPE, Integer.TYPE };
273       java.lang.reflect.Constructor cstr = cl.getConstructor(argClasses);
274       Object[] argObjects =
275         { this, new Integer(maxWidth), new Integer(maxHeight) };
276       vc = (VncCanvas)cstr.newInstance(argObjects);
277     } catch (Exception e) {
278       System.out.println("Warning: Java 2D API is not available");
279     }
280
281     // If we failed to create VncCanvas2D, use old VncCanvas.
282     if (vc == null)
283       vc = new VncCanvas(this, maxWidth, maxHeight);
284   }
285
286
287   //
288   // Process RFB socket messages.
289   // If the rfbThread is being stopped, ignore any exceptions,
290   // otherwise rethrow the exception so it can be handled.
291   //
292  
293   void processNormalProtocol() throws Exception {
294     try {
295       vc.processNormalProtocol();
296     } catch (Exception e) {
297       if (rfbThread == null) {
298         System.out.println("Ignoring RFB socket exceptions" +
299                            " because applet is stopping");
300       } else {
301         throw e;
302       }
303     }
304   }
305
306
307   //
308   // Connect to the RFB server and authenticate the user.
309   //
310
311   void connectAndAuthenticate() throws Exception
312   {
313     showConnectionStatus("Initializing...");
314     if (inSeparateFrame) {
315       vncFrame.pack();
316       vncFrame.show();
317     } else {
318       validate();
319     }
320
321     showConnectionStatus("Connecting to " + host + ", port " + port + "...");
322
323     rfb = new RfbProto(host, port, this);
324     showConnectionStatus("Connected to server");
325
326     rfb.readVersionMsg();
327     showConnectionStatus("RFB server supports protocol version " +
328                          rfb.serverMajor + "." + rfb.serverMinor);
329
330     rfb.writeVersionMsg();
331     showConnectionStatus("Using RFB protocol version " +
332                          rfb.clientMajor + "." + rfb.clientMinor);
333
334     int secType = rfb.negotiateSecurity();
335     int authType;
336     if (secType == RfbProto.SecTypeTight) {
337       showConnectionStatus("Enabling TightVNC protocol extensions");
338       rfb.initCapabilities();
339       rfb.setupTunneling();
340       authType = rfb.negotiateAuthenticationTight();
341     } else {
342       authType = secType;
343     }
344
345     switch (authType) {
346     case RfbProto.AuthNone:
347       showConnectionStatus("No authentication needed");
348       rfb.authenticateNone();
349       break;
350     case RfbProto.AuthVNC:
351       showConnectionStatus("Performing standard VNC authentication");
352       if (passwordParam != null) {
353         rfb.authenticateVNC(passwordParam);
354       } else {
355         String pw = askPassword();
356         rfb.authenticateVNC(pw);
357       }
358       break;
359     default:
360       throw new Exception("Unknown authentication scheme " + authType);
361     }
362   }
363
364
365   //
366   // Show a message describing the connection status.
367   // To hide the connection status label, use (msg == null).
368   //
369
370   void showConnectionStatus(String msg)
371   {
372     if (msg == null) {
373       if (vncContainer.isAncestorOf(connStatusLabel)) {
374         vncContainer.remove(connStatusLabel);
375       }
376       return;
377     }
378
379     System.out.println(msg);
380
381     if (connStatusLabel == null) {
382       connStatusLabel = new Label("Status: " + msg);
383       connStatusLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
384     } else {
385       connStatusLabel.setText("Status: " + msg);
386     }
387
388     if (!vncContainer.isAncestorOf(connStatusLabel)) {
389       GridBagConstraints gbc = new GridBagConstraints();
390       gbc.gridwidth = GridBagConstraints.REMAINDER;
391       gbc.fill = GridBagConstraints.HORIZONTAL;
392       gbc.anchor = GridBagConstraints.NORTHWEST;
393       gbc.weightx = 1.0;
394       gbc.weighty = 1.0;
395       gbc.insets = new Insets(20, 30, 20, 30);
396       gridbag.setConstraints(connStatusLabel, gbc);
397       vncContainer.add(connStatusLabel);
398     }
399
400     if (inSeparateFrame) {
401       vncFrame.pack();
402     } else {
403       validate();
404     }
405   }
406
407
408   //
409   // Show an authentication panel.
410   //
411
412   String askPassword() throws Exception
413   {
414     showConnectionStatus(null);
415
416     AuthPanel authPanel = new AuthPanel(this);
417
418     GridBagConstraints gbc = new GridBagConstraints();
419     gbc.gridwidth = GridBagConstraints.REMAINDER;
420     gbc.anchor = GridBagConstraints.NORTHWEST;
421     gbc.weightx = 1.0;
422     gbc.weighty = 1.0;
423     gbc.ipadx = 100;
424     gbc.ipady = 50;
425     gridbag.setConstraints(authPanel, gbc);
426     vncContainer.add(authPanel);
427
428     if (inSeparateFrame) {
429       vncFrame.pack();
430     } else {
431       validate();
432     }
433
434     authPanel.moveFocusToDefaultField();
435     String pw = authPanel.getPassword();
436     vncContainer.remove(authPanel);
437
438     return pw;
439   }
440
441
442   //
443   // Do the rest of the protocol initialisation.
444   //
445
446   void doProtocolInitialisation() throws IOException
447   {
448     rfb.writeClientInit();
449     rfb.readServerInit();
450
451     System.out.println("Desktop name is " + rfb.desktopName);
452     System.out.println("Desktop size is " + rfb.framebufferWidth + " x " +
453                        rfb.framebufferHeight);
454
455     setEncodings();
456
457     showConnectionStatus(null);
458   }
459
460
461   //
462   // Send current encoding list to the RFB server.
463   //
464
465   int[] encodingsSaved;
466   int nEncodingsSaved;
467
468   void setEncodings()        { setEncodings(false); }
469   void autoSelectEncodings() { setEncodings(true); }
470
471   void setEncodings(boolean autoSelectOnly) {
472     if (options == null || rfb == null || !rfb.inNormalProtocol)
473       return;
474
475     int preferredEncoding = options.preferredEncoding;
476     if (preferredEncoding == -1) {
477       long kbitsPerSecond = rfb.kbitsPerSecond();
478       if (nEncodingsSaved < 1) {
479         // Choose Tight or ZRLE encoding for the very first update.
480         System.out.println("Using Tight/ZRLE encodings");
481         preferredEncoding = RfbProto.EncodingTight;
482       } else if (kbitsPerSecond > 2000 &&
483                  encodingsSaved[0] != RfbProto.EncodingHextile) {
484         // Switch to Hextile if the connection speed is above 2Mbps.
485         System.out.println("Throughput " + kbitsPerSecond +
486                            " kbit/s - changing to Hextile encoding");
487         preferredEncoding = RfbProto.EncodingHextile;
488       } else if (kbitsPerSecond < 1000 &&
489                  encodingsSaved[0] != RfbProto.EncodingTight) {
490         // Switch to Tight/ZRLE if the connection speed is below 1Mbps.
491         System.out.println("Throughput " + kbitsPerSecond +
492                            " kbit/s - changing to Tight/ZRLE encodings");
493         preferredEncoding = RfbProto.EncodingTight;
494       } else {
495         // Don't change the encoder.
496         if (autoSelectOnly)
497           return;
498         preferredEncoding = encodingsSaved[0];
499       }
500     } else {
501       // Auto encoder selection is not enabled.
502       if (autoSelectOnly)
503         return;
504     }
505
506     int[] encodings = new int[20];
507     int nEncodings = 0;
508
509     encodings[nEncodings++] = preferredEncoding;
510     if (options.useCopyRect) {
511       encodings[nEncodings++] = RfbProto.EncodingCopyRect;
512     }
513
514     if (preferredEncoding != RfbProto.EncodingTight) {
515       encodings[nEncodings++] = RfbProto.EncodingTight;
516     }
517     if (preferredEncoding != RfbProto.EncodingZRLE) {
518       encodings[nEncodings++] = RfbProto.EncodingZRLE;
519     }
520     if (preferredEncoding != RfbProto.EncodingHextile) {
521       encodings[nEncodings++] = RfbProto.EncodingHextile;
522     }
523     if (preferredEncoding != RfbProto.EncodingZlib) {
524       encodings[nEncodings++] = RfbProto.EncodingZlib;
525     }
526     if (preferredEncoding != RfbProto.EncodingCoRRE) {
527       encodings[nEncodings++] = RfbProto.EncodingCoRRE;
528     }
529     if (preferredEncoding != RfbProto.EncodingRRE) {
530       encodings[nEncodings++] = RfbProto.EncodingRRE;
531     }
532
533     if (options.compressLevel >= 0 && options.compressLevel <= 9) {
534       encodings[nEncodings++] =
535         RfbProto.EncodingCompressLevel0 + options.compressLevel;
536     }
537     if (options.jpegQuality >= 0 && options.jpegQuality <= 9) {
538       encodings[nEncodings++] =
539         RfbProto.EncodingQualityLevel0 + options.jpegQuality;
540     }
541
542     if (options.requestCursorUpdates) {
543       encodings[nEncodings++] = RfbProto.EncodingXCursor;
544       encodings[nEncodings++] = RfbProto.EncodingRichCursor;
545       if (!options.ignoreCursorUpdates)
546         encodings[nEncodings++] = RfbProto.EncodingPointerPos;
547     }
548
549     encodings[nEncodings++] = RfbProto.EncodingLastRect;
550     encodings[nEncodings++] = RfbProto.EncodingNewFBSize;
551
552     boolean encodingsWereChanged = false;
553     if (nEncodings != nEncodingsSaved) {
554       encodingsWereChanged = true;
555     } else {
556       for (int i = 0; i < nEncodings; i++) {
557         if (encodings[i] != encodingsSaved[i]) {
558           encodingsWereChanged = true;
559           break;
560         }
561       }
562     }
563
564     if (encodingsWereChanged) {
565       try {
566         rfb.writeSetEncodings(encodings, nEncodings);
567         if (vc != null) {
568           vc.softCursorFree();
569         }
570       } catch (Exception e) {
571         e.printStackTrace();
572       }
573       encodingsSaved = encodings;
574       nEncodingsSaved = nEncodings;
575     }
576   }
577
578
579   //
580   // setCutText() - send the given cut text to the RFB server.
581   //
582
583   void setCutText(String text) {
584     try {
585       if (rfb != null && rfb.inNormalProtocol) {
586         rfb.writeClientCutText(text);
587       }
588     } catch (Exception e) {
589       e.printStackTrace();
590     }
591   }
592
593
594   //
595   // Order change in session recording status. To stop recording, pass
596   // null in place of the fname argument.
597   //
598
599   void setRecordingStatus(String fname) {
600     synchronized(recordingSync) {
601       sessionFileName = fname;
602       recordingStatusChanged = true;
603     }
604   }
605
606   //
607   // Start or stop session recording. Returns true if this method call
608   // causes recording of a new session.
609   //
610
611   boolean checkRecordingStatus() throws IOException {
612     synchronized(recordingSync) {
613       if (recordingStatusChanged) {
614         recordingStatusChanged = false;
615         if (sessionFileName != null) {
616           startRecording();
617           return true;
618         } else {
619           stopRecording();
620         }
621       }
622     }
623     return false;
624   }
625
626   //
627   // Start session recording.
628   //
629
630   protected void startRecording() throws IOException {
631     synchronized(recordingSync) {
632       if (!recordingActive) {
633         // Save settings to restore them after recording the session.
634         cursorUpdatesDef =
635           options.choices[options.cursorUpdatesIndex].getSelectedItem();
636         eightBitColorsDef =
637           options.choices[options.eightBitColorsIndex].getSelectedItem();
638         // Set options to values suitable for recording.
639         options.choices[options.cursorUpdatesIndex].select("Disable");
640         options.choices[options.cursorUpdatesIndex].setEnabled(false);
641         options.setEncodings();
642         options.choices[options.eightBitColorsIndex].select("No");
643         options.choices[options.eightBitColorsIndex].setEnabled(false);
644         options.setColorFormat();
645       } else {
646         rfb.closeSession();
647       }
648
649       System.out.println("Recording the session in " + sessionFileName);
650       rfb.startSession(sessionFileName);
651       recordingActive = true;
652     }
653   }
654
655   //
656   // Stop session recording.
657   //
658
659   protected void stopRecording() throws IOException {
660     synchronized(recordingSync) {
661       if (recordingActive) {
662         // Restore options.
663         options.choices[options.cursorUpdatesIndex].select(cursorUpdatesDef);
664         options.choices[options.cursorUpdatesIndex].setEnabled(true);
665         options.setEncodings();
666         options.choices[options.eightBitColorsIndex].select(eightBitColorsDef);
667         options.choices[options.eightBitColorsIndex].setEnabled(true);
668         options.setColorFormat();
669
670         rfb.closeSession();
671         System.out.println("Session recording stopped.");
672       }
673       sessionFileName = null;
674       recordingActive = false;
675     }
676   }
677
678
679   //
680   // readParameters() - read parameters from the html source or from the
681   // command line.  On the command line, the arguments are just a sequence of
682   // param_name/param_value pairs where the names and values correspond to
683   // those expected in the html applet tag source.
684   //
685
686   void readParameters() {
687     host = readParameter("HOST", !inAnApplet);
688     if (host == null) {
689       host = getCodeBase().getHost();
690       if (host.equals("")) {
691         fatalError("HOST parameter not specified");
692       }
693     }
694
695     String str = readParameter("PORT", true);
696     port = Integer.parseInt(str);
697
698     // Read "ENCPASSWORD" or "PASSWORD" parameter if specified.
699     readPasswordParameters();
700
701     if (inAnApplet) {
702       str = readParameter("Open New Window", false);
703       if (str != null && str.equalsIgnoreCase("Yes"))
704         inSeparateFrame = true;
705     }
706
707     // "Show Controls" set to "No" disables button panel.
708     showControls = true;
709     str = readParameter("Show Controls", false);
710     if (str != null && str.equalsIgnoreCase("No"))
711       showControls = false;
712
713     // "Offer Relogin" set to "No" disables "Login again" and "Close
714     // window" buttons under error messages in applet mode.
715     offerRelogin = true;
716     str = readParameter("Offer Relogin", false);
717     if (str != null && str.equalsIgnoreCase("No"))
718       offerRelogin = false;
719
720     // Do we continue showing desktop on remote disconnect?
721     showOfflineDesktop = false;
722     str = readParameter("Show Offline Desktop", false);
723     if (str != null && str.equalsIgnoreCase("Yes"))
724       showOfflineDesktop = true;
725
726     // Fine tuning options.
727     deferScreenUpdates = readIntParameter("Defer screen updates", 20);
728     deferCursorUpdates = readIntParameter("Defer cursor updates", 10);
729     deferUpdateRequests = readIntParameter("Defer update requests", 50);
730
731     // SocketFactory.
732     socketFactory = readParameter("SocketFactory", false);
733   }
734
735   //
736   // Read password parameters. If an "ENCPASSWORD" parameter is set,
737   // then decrypt the password into the passwordParam string. Otherwise,
738   // try to read the "PASSWORD" parameter directly to passwordParam.
739   //
740
741   private void readPasswordParameters() {
742     String encPasswordParam = readParameter("ENCPASSWORD", false);
743     if (encPasswordParam == null) {
744       passwordParam = readParameter("PASSWORD", false);
745     } else {
746       // ENCPASSWORD is hexascii-encoded. Decode.
747       byte[] pw = {0, 0, 0, 0, 0, 0, 0, 0};
748       int len = encPasswordParam.length() / 2;
749       if (len > 8)
750         len = 8;
751       for (int i = 0; i < len; i++) {
752         String hex = encPasswordParam.substring(i*2, i*2+2);
753         Integer x = new Integer(Integer.parseInt(hex, 16));
754         pw[i] = x.byteValue();
755       }
756       // Decrypt the password.
757       byte[] key = {23, 82, 107, 6, 35, 78, 88, 7};
758       DesCipher des = new DesCipher(key);
759       des.decrypt(pw, 0, pw, 0);
760       passwordParam = new String(pw);
761     }
762   }
763
764   public String readParameter(String name, boolean required) {
765     if (inAnApplet) {
766       String s = getParameter(name);
767       if ((s == null) && required) {
768         fatalError(name + " parameter not specified");
769       }
770       return s;
771     }
772
773     for (int i = 0; i < mainArgs.length; i += 2) {
774       if (mainArgs[i].equalsIgnoreCase(name)) {
775         try {
776           return mainArgs[i+1];
777         } catch (Exception e) {
778           if (required) {
779             fatalError(name + " parameter not specified");
780           }
781           return null;
782         }
783       }
784     }
785     if (required) {
786       fatalError(name + " parameter not specified");
787     }
788     return null;
789   }
790
791   int readIntParameter(String name, int defaultValue) {
792     String str = readParameter(name, false);
793     int result = defaultValue;
794     if (str != null) {
795       try {
796         result = Integer.parseInt(str);
797       } catch (NumberFormatException e) { }
798     }
799     return result;
800   }
801
802   //
803   // moveFocusToDesktop() - move keyboard focus either to VncCanvas.
804   //
805
806   void moveFocusToDesktop() {
807     if (vncContainer != null) {
808       if (vc != null && vncContainer.isAncestorOf(vc))
809         vc.requestFocus();
810     }
811   }
812
813   //
814   // disconnect() - close connection to server.
815   //
816
817   synchronized public void disconnect() {
818     System.out.println("Disconnect");
819
820     if (rfb != null && !rfb.closed())
821       rfb.close();
822     options.dispose();
823     clipboard.dispose();
824     if (rec != null)
825       rec.dispose();
826
827     if (inAnApplet) {
828       showMessage("Disconnected");
829     } else {
830       System.exit(0);
831     }
832   }
833
834   //
835   // fatalError() - print out a fatal error message.
836   // FIXME: Do we really need two versions of the fatalError() method?
837   //
838
839   synchronized public void fatalError(String str) {
840     System.out.println(str);
841
842     if (inAnApplet) {
843       // vncContainer null, applet not inited,
844       // can not present the error to the user.
845       Thread.currentThread().stop();
846     } else {
847       System.exit(1);
848     }
849   }
850
851   synchronized public void fatalError(String str, Exception e) {
852  
853     if (rfb != null && rfb.closed()) {
854       // Not necessary to show error message if the error was caused
855       // by I/O problems after the rfb.close() method call.
856       System.out.println("RFB thread finished");
857       return;
858     }
859
860     System.out.println(str);
861     e.printStackTrace();
862
863     if (rfb != null)
864       rfb.close();
865
866     if (inAnApplet) {
867       showMessage(str);
868     } else {
869       System.exit(1);
870     }
871   }
872
873   //
874   // Show message text and optionally "Relogin" and "Close" buttons.
875   //
876
877   void showMessage(String msg) {
878     vncContainer.removeAll();
879
880     Label errLabel = new Label(msg, Label.CENTER);
881     errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
882
883     if (offerRelogin) {
884
885       Panel gridPanel = new Panel(new GridLayout(0, 1));
886       Panel outerPanel = new Panel(new FlowLayout(FlowLayout.LEFT));
887       outerPanel.add(gridPanel);
888       vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 16));
889       vncContainer.add(outerPanel);
890       Panel textPanel = new Panel(new FlowLayout(FlowLayout.CENTER));
891       textPanel.add(errLabel);
892       gridPanel.add(textPanel);
893       gridPanel.add(new ReloginPanel(this));
894
895     } else {
896
897       vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30));
898       vncContainer.add(errLabel);
899
900     }
901
902     if (inSeparateFrame) {
903       vncFrame.pack();
904     } else {
905       validate();
906     }
907   }
908
909   //
910   // Stop the applet.
911   // Main applet thread will terminate on first exception
912   // after seeing that rfbThread has been set to null.
913   //
914
915   public void stop() {
916     System.out.println("Stopping applet");
917     rfbThread = null;
918   }
919
920   //
921   // This method is called before the applet is destroyed.
922   //
923
924   public void destroy() {
925     System.out.println("Destroying applet");
926
927     vncContainer.removeAll();
928     options.dispose();
929     clipboard.dispose();
930     if (rec != null)
931       rec.dispose();
932     if (rfb != null && !rfb.closed())
933       rfb.close();
934     if (inSeparateFrame)
935       vncFrame.dispose();
936   }
937
938   //
939   // Start/stop receiving mouse events.
940   //
941
942   public void enableInput(boolean enable) {
943     vc.enableInput(enable);
944   }
945
946   //
947   // Close application properly on window close event.
948   //
949
950   public void windowClosing(WindowEvent evt) {
951     System.out.println("Closing window");
952     if (rfb != null)
953       disconnect();
954
955     vncContainer.hide();
956
957     if (!inAnApplet) {
958       System.exit(0);
959     }
960   }
961
962   //
963   // Ignore window events we're not interested in.
964   //
965
966   public void windowActivated(WindowEvent evt) {}
967   public void windowDeactivated (WindowEvent evt) {}
968   public void windowOpened(WindowEvent evt) {}
969   public void windowClosed(WindowEvent evt) {}
970   public void windowIconified(WindowEvent evt) {}
971   public void windowDeiconified(WindowEvent evt) {}
972 }