Implemented rfb/Configuration similar to the native client methods. Added equivalent cmd line options for all native client options except "-menuKey", which needs a little more work on the GUI side before it can be added.

git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4913 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/java/com/tigervnc/rfb/AliasParameter.java b/java/com/tigervnc/rfb/AliasParameter.java
index 91dfffd..120497c 100644
--- a/java/com/tigervnc/rfb/AliasParameter.java
+++ b/java/com/tigervnc/rfb/AliasParameter.java
@@ -1,4 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright 2004-2005 Cendio AB.
+ * Copyright 2012 Brian P. Hinz
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,9 +21,15 @@
 package com.tigervnc.rfb;
 
 public class AliasParameter extends VoidParameter {
-  public AliasParameter(String name_, String desc_, VoidParameter v) {
-    super(name_, desc_);
-    param = v;
+  public AliasParameter(String name_, String desc_, VoidParameter param_, 
+                        Configuration.ConfigurationObject co)
+  {
+    super(name_, desc_, co);
+    param = param_;
+  }
+
+  public AliasParameter(String name_, String desc_, VoidParameter param_) {
+    this(name_, desc_, param_, Configuration.ConfigurationObject.ConfGlobal);
   }
 
   public boolean setParam(String v) { return param.setParam(v); }
@@ -31,5 +39,10 @@
   public String getValueStr() { return param.getValueStr(); }
   public boolean isBool() { return param.isBool(); }
 
+  public void setImmutable() {
+    vlog.debug("set immutable "+getName()+" (Alias)");
+    param.setImmutable();
+  }
+
   protected VoidParameter param;
 }
diff --git a/java/com/tigervnc/rfb/BoolParameter.java b/java/com/tigervnc/rfb/BoolParameter.java
index c484a4c..0806be4 100644
--- a/java/com/tigervnc/rfb/BoolParameter.java
+++ b/java/com/tigervnc/rfb/BoolParameter.java
@@ -1,4 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright 2004-2005 Cendio AB.
+ * Copyright 2012 Brian P. Hinz
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,26 +21,39 @@
 package com.tigervnc.rfb;
 
 public class BoolParameter extends VoidParameter {
-  public BoolParameter(String name_, String desc_, boolean v) {
-    super(name_, desc_);
+  public BoolParameter(String name_, String desc_, boolean v,
+                       Configuration.ConfigurationObject co)
+  {
+    super(name_, desc_, co);
     value = v;
     defValue = v;
   }
 
+  public BoolParameter(String name_, String desc_, boolean v) {
+    this(name_, desc_, v, Configuration.ConfigurationObject.ConfGlobal);
+  }
+
   public boolean setParam(String v) {
-    if (v.equals("1") || v.equalsIgnoreCase("on") ||
+    if (immutable) return true;
+
+    if (v == null || v.equals("1") || v.equalsIgnoreCase("on") ||
         v.equalsIgnoreCase("true") || v.equalsIgnoreCase("yes"))
       value = true;
     else if (v.equals("0") || v.equalsIgnoreCase("off") ||
         v.equalsIgnoreCase("false") || v.equalsIgnoreCase("no"))
       value = false;
-    else
+    else {
+      vlog.error("Bool parameter "+getName()+": invalid value '"+v+"'");
       return false;
+    }
     return true;
   }
 
   public boolean setParam() { setParam(true); return true; }
-  public void setParam(boolean b) { value = b; }
+  public void setParam(boolean b) { 
+    if (immutable) return;
+    value = b;
+  }
 
   public String getDefaultStr() { return defValue ? "1" : "0"; }
   public String getValueStr() { return value ? "1" : "0"; }
diff --git a/java/com/tigervnc/rfb/CSecurityTLS.java b/java/com/tigervnc/rfb/CSecurityTLS.java
index 8794edf..c7e3108 100644
--- a/java/com/tigervnc/rfb/CSecurityTLS.java
+++ b/java/com/tigervnc/rfb/CSecurityTLS.java
@@ -44,10 +44,10 @@
 
   public static StringParameter x509ca
   = new StringParameter("x509ca",
-                        "X509 CA certificate", "");
+                        "X509 CA certificate", "", Configuration.ConfigurationObject.ConfViewer);
   public static StringParameter x509crl
   = new StringParameter("x509crl",
-                        "X509 CRL file", "");
+                        "X509 CRL file", "", Configuration.ConfigurationObject.ConfViewer);
 
   private void initGlobal() 
   {
diff --git a/java/com/tigervnc/rfb/Configuration.java b/java/com/tigervnc/rfb/Configuration.java
index f397c85..de8cfc7 100644
--- a/java/com/tigervnc/rfb/Configuration.java
+++ b/java/com/tigervnc/rfb/Configuration.java
@@ -1,5 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
  * Copyright 2004-2005 Cendio AB.
+ * Copyright 2012 Brian P. Hinz
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,68 +26,250 @@
 
 public class Configuration {
 
+  static LogWriter vlog = new LogWriter("Config");
+
+  public enum ConfigurationObject { ConfGlobal, ConfServer, ConfViewer };
+
+  // -=- The Global/server/viewer Configuration objects
+  private static Configuration global_ = null;
+  private static Configuration server_ = null;
+  private static Configuration viewer_ = null;
+
+  public static Configuration global() {
+    if (global_ == null)
+      global_ = new Configuration("Global");
+    return global_;
+  } 
+
+  public static Configuration server() {
+    if (server_ == null)
+      server_ = new Configuration("Server");
+    return server_;
+  } 
+
+  public static Configuration viewer() {
+    if (viewer_ == null)
+      viewer_ = new Configuration("Viewer");
+    return viewer_;
+  } 
+
+  // Enable server/viewer specific parameters
+  public static void enableServerParams() { global().appendConfiguration(server()); }
+  public static void enableViewerParams() { global().appendConfiguration(viewer()); }
+
+  // Append configuration object to this instance.
+  // NOTE: conf instance can be only one configuration object
+  public void appendConfiguration(Configuration conf) {
+    conf._next = _next; _next = conf;
+  }
+
+  // -=- Configuration implementation
+  public Configuration(String name_, Configuration attachToGroup) {
+    name = name_; head = null; _next = null;
+    if (attachToGroup != null) {
+      _next = attachToGroup._next;
+      attachToGroup._next = this;
+    }
+  }
+
+  public Configuration(String name_) {
+    this(name_, null);
+  }
+
+  // - Return the buffer containing the Configuration's name
+  final public String getName() { return name; }
+
+  // - Assignment operator.  For every Parameter in this Configuration's
+  //   group, get()s the corresponding source parameter and copies its
+  //   content.
+  public Configuration assign(Configuration src) {
+    VoidParameter current = head;
+    while (current != null) {
+      VoidParameter srcParam = ((Configuration)src).get(current.getName());
+      if (srcParam != null) {
+        current.immutable = false;
+        String value = srcParam.getValueStr();
+        vlog.debug("operator=("+current.getName()+", "+value+")");
+        current.setParam(value);
+      }
+      current = current._next;
+    }
+    if (_next != null)
+      _next = src;
+    return this;
+  }
+
   // - Set named parameter to value
-  public static boolean setParam(String name, String value) {
-    VoidParameter param = getParam(name);
-    if (param == null) return false;
-    return param.setParam(value);
+  public boolean set(String n, String v, boolean immutable) {
+    return set(n, n.length(), v, immutable);
+  }
+
+  public boolean set(String n, String v) {
+    return set(n, n.length(), v, false);
   }
 
   // - Set parameter to value (separated by "=")
-  public static boolean setParam(String config) {
+  public boolean set(String name, int len,
+                     String val, boolean immutable)
+  {
+    VoidParameter current = head;
+    while (current != null) {
+      if (current.getName().length() == len &&
+          current.getName().equalsIgnoreCase(name.substring(0, len)))
+      {
+        boolean b = current.setParam(val);
+        current.setHasBeenSet(); 
+        if (b && immutable) 
+  	  current.setImmutable();
+        return b;
+      }
+      current = current._next;
+    }
+    return (_next != null) ? _next.set(name, len, val, immutable) : false;
+  }
+
+  // - Set named parameter to value, with name truncated at len
+  boolean set(String config, boolean immutable) {
     boolean hyphen = false;
     if (config.charAt(0) == '-') {
       hyphen = true;
-      if (config.charAt(1) == '-')
-        config = config.substring(2); // allow gnu-style --<option>
-      else
-        config = config.substring(1);
+      config = config.substring(1);
+      if (config.charAt(0) == '-') config = config.substring(1); // allow gnu-style --<option>
     }
     int equal = config.indexOf('=');
-    if (equal != -1) {
-      return setParam(config.substring(0, equal), config.substring(equal+1));
+    if (equal > -1) {
+      return set(config, equal, config.substring(equal+1), immutable);
     } else if (hyphen) {
-      VoidParameter param = getParam(config);
-      if (param == null) return false;
-      return param.setParam();
-    }
-    return false;
+      VoidParameter current = head;
+      while (current != null) {
+        if (current.getName().equalsIgnoreCase(config)) {
+          boolean b = current.setParam();
+  	  current.setHasBeenSet(); 
+          if (b && immutable) 
+  	    current.setImmutable();
+          return b;
+        }
+        current = current._next;
+      }
+    }    
+    return (_next != null) ? _next.set(config, immutable) : false;
   }
 
+  boolean set(String config) {
+    return set(config, false);
+  }
+
+  // - Container for process-wide Global parameters
+  public static boolean setParam(String param, String value, boolean immutable) {
+    return global().set(param, value, immutable);
+  }
+
+  public static boolean setParam(String param, String value) {
+    return setParam(param, value, false);
+  }
+
+  public static boolean setParam(String config, boolean immutable) { 
+    return global().set(config, immutable);
+  }
+
+  public static boolean setParam(String config) { 
+    return setParam(config, false);
+  }
+
+  public static boolean setParam(String name, int len,
+                                 String val, boolean immutable) {
+    return global().set(name, len, val, immutable);
+  }
+
+
   // - Get named parameter
-  public static VoidParameter getParam(String name) {
+  public VoidParameter get(String param)
+  {
     VoidParameter current = head;
     while (current != null) {
-      if (name.equalsIgnoreCase(current.getName()))
+      if (current.getName().equalsIgnoreCase(param))
         return current;
-      current = current.next;
+      current = current._next;
     }
-    return null;
+    return (_next != null) ? _next.get(param) : null;
+  }
+  
+  public static VoidParameter getParam(String param) { return global().get(param); }
+
+  public static void listParams(int width, int nameWidth) {
+    global().list(width, nameWidth);
+  }
+  public static void listParams() {
+    listParams(79, 10);
   }
 
-  public static String listParams() {
-    StringBuffer s = new StringBuffer();
-
+  public void list(int width, int nameWidth) {
     VoidParameter current = head;
+  
+    System.err.format("%s Parameters:%n", name);
     while (current != null) {
       String def_str = current.getDefaultStr();
-      String desc = current.getDescription();
-      s.append("  "+current.getName()+" - "+desc+" (default="+def_str+")\n");
-      current = current.next;
-    }
+      String desc = current.getDescription().trim();
+      String format = "  %-"+nameWidth+"s -";
+      System.err.format(format, current.getName());
+      int column = current.getName().length();
+      if (column < nameWidth) column = nameWidth;
+      column += 4;
+      while (true) {
+        int s = desc.indexOf(' ');
+        int wordLen;
+        if (s > -1) wordLen = s;
+        else wordLen = desc.length();
+  
+        if (column + wordLen + 1 > width) {
+          format = "%n%"+(nameWidth+4)+"s";
+          System.err.format(format, "");
+          column = nameWidth+4;
+        }
+        format = " %"+wordLen+"s";
+        System.err.format(format, desc.substring(0, wordLen));
+        column += wordLen + 1;
 
-    return s.toString();
+        if (s == -1) break;
+        desc = desc.substring(wordLen+1);
+      }
+  
+      if (def_str != null) {
+        if (column + def_str.length() + 11 > width)
+          System.err.format("%n%"+(nameWidth+4)+"s","");
+        System.err.format(" (default=%s)%n",def_str);
+        def_str = null;
+      } else {
+        System.err.format("%n");
+      }
+      current = current._next;
+    }
+  
+    if (_next != null)
+      _next.list(width, nameWidth);
   }
 
-  public static void readAppletParams(java.applet.Applet applet) {
+  public void list() {
+    list(79, 10);
+  }
+
+  public void readAppletParams(java.applet.Applet applet) {
     VoidParameter current = head;
     while (current != null) {
       String str = applet.getParameter(current.getName());
       if (str != null)
         current.setParam(str);
-      current = current.next;
+      current = current._next;
     }
   }
 
-  public static VoidParameter head;
+  // Name for this Configuration
+  private String name;
+
+  // - Pointer to first Parameter in this group
+  public VoidParameter head;
+
+  // Pointer to next Configuration in this group
+  public Configuration _next;
+
 }
diff --git a/java/com/tigervnc/rfb/IntParameter.java b/java/com/tigervnc/rfb/IntParameter.java
index c794c6b..d949710 100644
--- a/java/com/tigervnc/rfb/IntParameter.java
+++ b/java/com/tigervnc/rfb/IntParameter.java
@@ -1,4 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright 2004-2005 Cendio AB.
+ * Copyright 2012 Brian P. Hinz
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,26 +21,55 @@
 package com.tigervnc.rfb;
 
 public class IntParameter extends VoidParameter {
-  public IntParameter(String name_, String desc_, int v) {
-    super(name_, desc_);
+  public IntParameter(String name_, String desc_, int v,
+                      int minValue_, int maxValue_, 
+                      Configuration.ConfigurationObject co)
+  {
+    super(name_, desc_, co);
     value = v;
     defValue = v;
+    minValue = minValue_;
+    maxValue = maxValue_;
+  }
+
+  public IntParameter(String name_, String desc_, int v) 
+  {
+    this(name_, desc_, v, Integer.MIN_VALUE, Integer.MAX_VALUE,
+         Configuration.ConfigurationObject.ConfGlobal);
   }
 
   public boolean setParam(String v) {
+    if (immutable) return true;
+    vlog.debug("set "+getName()+"(Int) to "+v);
     try {
-      value = Integer.parseInt(v);
+      int i;
+      i = Integer.parseInt(v);
+      if (i < minValue || i > maxValue)
+        return false;
+      value = i;
+      return true;
     } catch (NumberFormatException e) {
-      return false;
+      throw new Exception(e.toString());
     }
+  }
+
+  public boolean setParam(int v) {
+    if (immutable) return true;
+    vlog.debug("set "+getName()+"(Int) to "+v);
+    if (v < minValue || v > maxValue)
+      return false;
+    value = v;
     return true;
   }
 
   public String getDefaultStr() { return Integer.toString(defValue); }
   public String getValueStr() { return Integer.toString(value); }
 
+  //public int int() { return value; }
   public int getValue() { return value; }
 
   protected int value;
   protected int defValue;
+  protected int minValue;
+  protected int maxValue;
 }
diff --git a/java/com/tigervnc/rfb/SecurityClient.java b/java/com/tigervnc/rfb/SecurityClient.java
index f322f81..3fb919a 100644
--- a/java/com/tigervnc/rfb/SecurityClient.java
+++ b/java/com/tigervnc/rfb/SecurityClient.java
@@ -1,6 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
  * Copyright (C) 2010 TigerVNC Team
- * Copyright (C) 2011 Brian P. Hinz
+ * Copyright (C) 2011-2012 Brian P. Hinz
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -81,6 +81,6 @@
   static StringParameter secTypes 
   = new StringParameter("SecurityTypes",
                         "Specify which security scheme to use (None, VncAuth)",
-                        "Ident,TLSIdent,X509Ident,X509Plain,TLSPlain,X509Vnc,TLSVnc,X509None,TLSNone,VncAuth,None");
+                        "Ident,TLSIdent,X509Ident,X509Plain,TLSPlain,X509Vnc,TLSVnc,X509None,TLSNone,VncAuth,None", Configuration.ConfigurationObject.ConfViewer);
 
 }
diff --git a/java/com/tigervnc/rfb/StringParameter.java b/java/com/tigervnc/rfb/StringParameter.java
index 9b0e884..4805038 100644
--- a/java/com/tigervnc/rfb/StringParameter.java
+++ b/java/com/tigervnc/rfb/StringParameter.java
@@ -1,4 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright 2004-2005 Cendio AB.
+ * Copyright 2012 Brian P. Hinz
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,20 +21,29 @@
 package com.tigervnc.rfb;
 
 public class StringParameter extends VoidParameter {
-  public StringParameter(String name_, String desc_, String v) {
-    super(name_, desc_);
+  public StringParameter(String name_, String desc_, String v, 
+                         Configuration.ConfigurationObject co) 
+  {
+    super(name_, desc_, co);
     value = v;
     defValue = v;
   }
 
-  public boolean setParam(String v) {
-    value = v;
-    return value != null;
+  public StringParameter(String name_, String desc_, String v) 
+  {
+    this(name_, desc_, v, Configuration.ConfigurationObject.ConfGlobal);
   }
 
-  public boolean setDefaultStr(String v) {
+  public void setDefaultStr(String v) {
     value = defValue = v;
-    return defValue != null;
+  }
+
+  public boolean setParam(String v) {
+    if (immutable) return true;
+    if (v == null)
+      throw new Exception("setParam(<null>) not allowed");
+    value = v;
+    return value != null;
   }
 
   public String getDefaultStr() { return defValue; }
diff --git a/java/com/tigervnc/rfb/VoidParameter.java b/java/com/tigervnc/rfb/VoidParameter.java
index fa48e4b..33c8bcb 100644
--- a/java/com/tigervnc/rfb/VoidParameter.java
+++ b/java/com/tigervnc/rfb/VoidParameter.java
@@ -1,4 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright 2004-2005 Cendio AB.
+ * Copyright 2012 Brian P. Hinz
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,23 +21,62 @@
 package com.tigervnc.rfb;
 
 abstract public class VoidParameter {
-  public VoidParameter(String name_, String desc_) {
-    name = name_;
-    description = desc_;
-    next = Configuration.head;
-    Configuration.head = this;
+  public VoidParameter(String name_, String desc_, 
+                       Configuration.ConfigurationObject co) 
+  {
+    immutable = false; _hasBeenSet = false; name = name_; description = desc_;
+
+    Configuration conf = null;
+
+    switch (co) {
+    case ConfGlobal:
+      conf = Configuration.global();
+      break;
+    case ConfServer:
+      conf = Configuration.server();
+      break;
+    case ConfViewer:
+      conf = Configuration.viewer();
+      break;
+    }
+    _next = conf.head;
+    conf.head = this;
   }
 
-  final public String getName() { return name; }
-  final public String getDescription() { return description; }
+  public VoidParameter(String name_, String desc_) {
+    this(name_, desc_, Configuration.ConfigurationObject.ConfGlobal);
+  }
+
+  final public String getName() { 
+    return name;
+  }
+
+  final public String getDescription() { 
+    return description;
+  }
 
   abstract public boolean setParam(String value);
   public boolean setParam() { return false; }
   abstract public String getDefaultStr();
   abstract public String getValueStr();
   public boolean isBool() { return false; }
+  public void setImmutable() {
+    vlog.debug("set immutable "+getName());
+    immutable = true;
+  }
 
-  public VoidParameter next;
+  public void setHasBeenSet() {
+    _hasBeenSet = true;
+  }
+
+  public boolean hasBeenSet() {
+    return _hasBeenSet;
+  }
+
   protected String name;
   protected String description;
+  public VoidParameter _next;
+  protected boolean immutable;
+  protected boolean _hasBeenSet;
+  static LogWriter vlog = new LogWriter("VoidParameter");
 }
diff --git a/java/com/tigervnc/vncviewer/CConn.java b/java/com/tigervnc/vncviewer/CConn.java
index 8c95017..9278148 100644
--- a/java/com/tigervnc/vncviewer/CConn.java
+++ b/java/com/tigervnc/vncviewer/CConn.java
@@ -809,10 +809,8 @@
       pkgTime = attributes.getValue("Package-Time");
     } catch (IOException e) { }
     JOptionPane.showMessageDialog((viewport != null ? viewport : null),
-      VncViewer.about1+" v"+VncViewer.version+" ("+VncViewer.build+")\n"
-      +"Built on "+pkgDate+" at "+pkgTime+"\n"
-      +VncViewer.about2+"\n"
-      +VncViewer.about3,
+      String.format(VncViewer.aboutText, VncViewer.version, VncViewer.build,
+                    VncViewer.buildDate, VncViewer.buildTime), 
       "About TigerVNC Viewer for Java",
       JOptionPane.INFORMATION_MESSAGE,
       logo);
diff --git a/java/com/tigervnc/vncviewer/VncViewer.java b/java/com/tigervnc/vncviewer/VncViewer.java
index a62d736..e433bf9 100644
--- a/java/com/tigervnc/vncviewer/VncViewer.java
+++ b/java/com/tigervnc/vncviewer/VncViewer.java
@@ -52,13 +52,15 @@
 
 public class VncViewer extends java.applet.Applet implements Runnable
 {
-  public static final String about1 = "TigerVNC Viewer for Java";
-  public static final String about2 = "Copyright (C) 1998-2011 "+
-                                      "TigerVNC Team and many others (see README)";
-  public static final String about3 = "Visit http://www.tigervnc.org "+
-                                      "for information on TigerVNC.";
+  public static final String aboutText = new String("TigerVNC Java Viewer v%s (%s)%n"+
+                                                    "Built on %s at %s%n"+
+                                                    "Copyright (C) 1999-2011 TigerVNC Team and many others (see README.txt)%n"+
+                                                    "See http://www.tigervnc.org for information on TigerVNC.");
+                                           
   public static String version = null;
   public static String build = null;
+  public static String buildDate = null;
+  public static String buildTime = null;
 
   public static void setLookAndFeel() {
     try {
@@ -114,7 +116,17 @@
   
   public VncViewer(String[] argv) {
     applet = false;
+
+    SecurityClient.setDefaults();
     
+    // Write about text to console, still using normal locale codeset
+    getTimestamp();
+    System.err.format("%n");
+    System.err.format(aboutText, version, build, buildDate, buildTime); 
+    System.err.format("%n");
+
+    Configuration.enableViewerParams();
+
     // Override defaults with command-line options
     for (int i = 0; i < argv.length; i++) {
       if (argv[i].equalsIgnoreCase("-log")) {
@@ -148,6 +160,23 @@
       vncServerName.setParam(argv[i]);
     }
 
+    if (!autoSelect.hasBeenSet()) {
+      // Default to AutoSelect=0 if -PreferredEncoding or -FullColor is used
+      autoSelect.setParam(!preferredEncoding.hasBeenSet() &&
+                          !fullColour.hasBeenSet() &&
+                          !fullColourAlias.hasBeenSet());
+    }
+    if (!fullColour.hasBeenSet() && !fullColourAlias.hasBeenSet()) {
+      // Default to FullColor=0 if AutoSelect=0 && LowColorLevel is set
+      if (!autoSelect.getValue() && (lowColourLevel.hasBeenSet() ||
+                          lowColourLevelAlias.hasBeenSet())) {
+        fullColour.setParam(false);
+      }
+    }
+    if (!customCompressLevel.hasBeenSet()) {
+      // Default to CustomCompressLevel=1 if CompressLevel is used.
+      customCompressLevel.setParam(compressLevel.hasBeenSet());
+    }
   }
 
   public static void usage() {
@@ -166,62 +195,52 @@
                     "Other valid forms are <param>=<value> -<param>=<value> "+
                     "--<param>=<value>\n"+
                     "Parameter names are case-insensitive.  The parameters "+
-                    "are:");
+                    "are:\n"+
+                    "\n");
     System.err.print(usage);
-    VoidParameter current = Configuration.head;
-    while (current != null) {
-      System.err.format("%n%-7s%-64s%n", " ", "\u001B[1m"+"-"+current.getName()+"\u001B[0m");
-      String[] desc = current.getDescription().split("(?<=\\G.{60})");
-      for (int i = 0; i < desc.length; i++) {
-        String line = desc[i];
-        if (!line.endsWith(" ") && i < desc.length-1)
-          line = line+"-";
-        System.err.format("%-14s%-64s%n"," ", line.trim());
-      }
-      System.err.format("%-14s%-64s%n"," ", "[default="+current.getDefaultStr()+"]");
-      current = current.next;
-    }
+
+    Configuration.listParams(79, 14);
     String propertiesString = ("\n"+
 "\u001B[1mSystem Properties\u001B[0m (adapted from the TurboVNC vncviewer man page)\n"+
-"\tWhen started with the -via option, vncviewer reads the\n"+
-"\t\u001B[1mcom.tigervnc.VNC_VIA_CMD\u001B[0m System property, expands\n"+
-"\tpatterns beginning with the \"%\" character, and uses the resulting\n"+
-"\tcommand line to establish the secure tunnel to the VNC gateway.\n"+
-"\tIf \u001B[1mcom.tigervnc.VNC_VIA_CMD\u001B[0m is not set, this \n"+
-"\tcommand line defaults to \"/usr/bin/ssh -f -L %L:%H:%R %G sleep 20\".\n"+
+"  When started with the -via option, vncviewer reads the\n"+
+"  \u001B[1mcom.tigervnc.VNC_VIA_CMD\u001B[0m System property, expands\n"+
+"  patterns beginning with the \"%\" character, and uses the resulting\n"+
+"  command line to establish the secure tunnel to the VNC gateway.\n"+
+"  If \u001B[1mcom.tigervnc.VNC_VIA_CMD\u001B[0m is not set, this \n"+
+"  command line defaults to \"/usr/bin/ssh -f -L %L:%H:%R %G sleep 20\".\n"+
 "\n"+
-"\tThe following patterns are recognized in the VNC_VIA_CMD property\n"+
-"\t(note that all of the patterns %G, %H, %L and %R must be present in \n"+
-"\tthe command template):\n"+
+"  The following patterns are recognized in the VNC_VIA_CMD property\n"+
+"  (note that all of the patterns %G, %H, %L and %R must be present in \n"+
+"  the command template):\n"+
 "\n"+
-"\t\t%%     A literal \"%\";\n"+
+"  \t%%     A literal \"%\";\n"+
 "\n"+
-"\t\t%G     gateway machine name;\n"+
+"  \t%G     gateway machine name;\n"+
 "\n"+
-"\t\t%H     remote VNC machine name, (as known to the gateway);\n"+
+"  \t%H     remote VNC machine name, (as known to the gateway);\n"+
 "\n"+
-"\t\t%L     local TCP port number;\n"+
+"  \t%L     local TCP port number;\n"+
 "\n"+
-"\t\t%R     remote TCP port number.\n"+
+"  \t%R     remote TCP port number.\n"+
 "\n"+
-"\tWhen started with the -tunnel option, vncviewer reads the\n"+
-"\t\u001B[1mcom.tigervnc.VNC_TUNNEL_CMD\u001B[0m System property, expands\n"+
-"\tpatterns beginning with the \"%\" character, and uses the resulting\n"+
-"\tcommand line to establish the secure tunnel to the VNC server.\n"+
-"\tIf \u001B[1mcom.tigervnc.VNC_TUNNEL_CMD\u001B[0m is not set, this command \n"+
-"\tline defaults to \"/usr/bin/ssh -f -L %L:localhost:%R %H sleep 20\".\n"+
+"  When started with the -tunnel option, vncviewer reads the\n"+
+"  \u001B[1mcom.tigervnc.VNC_TUNNEL_CMD\u001B[0m System property, expands\n"+
+"  patterns beginning with the \"%\" character, and uses the resulting\n"+
+"  command line to establish the secure tunnel to the VNC server.\n"+
+"  If \u001B[1mcom.tigervnc.VNC_TUNNEL_CMD\u001B[0m is not set, this command \n"+
+"  line defaults to \"/usr/bin/ssh -f -L %L:localhost:%R %H sleep 20\".\n"+
 "\n"+
-"\tThe following patterns are recognized in the VNC_TUNNEL_CMD property\n"+
-"\t(note that all of the patterns %H, %L and %R must be present in \n"+
-"\tthe command template):\n"+
+"  The following patterns are recognized in the VNC_TUNNEL_CMD property\n"+
+"  (note that all of the patterns %H, %L and %R must be present in \n"+
+"  the command template):\n"+
 "\n"+
-"\t\t%%     A literal \"%\";\n"+
+"  \t%%     A literal \"%\";\n"+
 "\n"+
-"\t\t%H     remote VNC machine name (as known to the client);\n"+
+"  \t%H     remote VNC machine name (as known to the client);\n"+
 "\n"+
-"\t\t%L     local TCP port number;\n"+
+"  \t%L     local TCP port number;\n"+
 "\n"+
-"\t\t%R     remote TCP port number.\n"+
+"  \t%R     remote TCP port number.\n"+
 "\n");
     System.err.print(propertiesString);
     System.exit(1);
@@ -259,8 +278,7 @@
     logo = icon.getImage();
   }
 
-  public void start() {
-    vlog.debug("start called");
+  private void getTimestamp() {
     if (version == null || build == null) {
       ClassLoader cl = this.getClass().getClassLoader();
       InputStream stream = cl.getResourceAsStream("com/tigervnc/vncviewer/timestamp");
@@ -269,12 +287,19 @@
         Attributes attributes = manifest.getMainAttributes();
         version = attributes.getValue("Version");
         build = attributes.getValue("Build");
+        buildDate = attributes.getValue("Package-Date");
+        buildTime = attributes.getValue("Package-Time");
       } catch (java.io.IOException e) { }
     }
+  }
+
+  public void start() {
+    vlog.debug("start called");
+    getTimestamp();
     nViewers++;
     if (applet && firstApplet) {
       alwaysShowServerDialog.setParam(true);
-      Configuration.readAppletParams(this);
+      Configuration.global().readAppletParams(this);
       String host = getCodeBase().getHost();
       if (vncServerName.getValue() == null && vncServerPort.getValue() != 0) {
         int port = vncServerPort.getValue();
@@ -294,11 +319,8 @@
   public void paint(Graphics g) {
     g.drawImage(logo, 0, 0, this);
     int h = logo.getHeight(this)+20;
-    g.drawString(about1+" v"+version+" ("+build+")", 0, h);
-    h += g.getFontMetrics().getHeight();
-    g.drawString(about2, 0, h);
-    h += g.getFontMetrics().getHeight();
-    g.drawString(about3, 0, h);
+    g.drawString(String.format(aboutText, version, build, 
+                               buildDate, buildTime), 0, h);
   }
 
   public void run() {
@@ -382,10 +404,20 @@
                       "used until AutoSelect decides the link is "+
                       "fast enough",
                       true);
-  AliasParameter fullColor
+  AliasParameter fullColourAlias
   = new AliasParameter("FullColor",
                        "Alias for FullColour",
                        fullColour);
+  IntParameter lowColourLevel
+  = new IntParameter("LowColorLevel",
+                     "Color level to use on slow connections. "+
+                     "0 = Very Low (8 colors), 1 = Low (64 colors), "+
+                     "2 = Medium (256 colors)", 
+                     2);
+  AliasParameter lowColourLevelAlias
+  = new AliasParameter("LowColourLevel",
+                       "Alias for LowColorLevel",
+                       lowColourLevel);
   StringParameter preferredEncoding
   = new StringParameter("PreferredEncoding",
                         "Preferred encoding to use (Tight, ZRLE, "+
@@ -413,6 +445,11 @@
   = new BoolParameter("SendClipboard",
                       "Send clipboard changes to the server",
                       true);
+  // FIXME
+  //StringParameter menuKey
+  //= new StringParameter("MenuKey",
+  //                      "The key which brings up the popup menu",
+  //                      "F8");
   StringParameter desktopSize
   = new StringParameter("DesktopSize",
                         "Reconfigure desktop size on the server on "+