You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Parameters.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. /* Copyright (C) 2016 Brian P. Hinz
  2. *
  3. * This is free software; you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation; either version 2 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This software is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License
  14. * along with this software; if not, write to the Free Software
  15. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
  16. * USA.
  17. */
  18. package com.tigervnc.vncviewer;
  19. import java.io.File;
  20. import java.io.FileNotFoundException;
  21. import java.io.FileReader;
  22. import java.io.IOException;
  23. import java.io.LineNumberReader;
  24. import java.io.PrintWriter;
  25. import java.util.StringTokenizer;
  26. import com.tigervnc.rfb.*;
  27. import com.tigervnc.rfb.Exception;
  28. public class Parameters {
  29. public static BoolParameter noLionFS
  30. = new BoolParameter("NoLionFS",
  31. "On Mac systems, setting this parameter will force the use of the old "+
  32. "(pre-Lion) full-screen mode, even if the viewer is running on OS X 10.7 "+
  33. "Lion or later.",
  34. false);
  35. public static BoolParameter embed
  36. = new BoolParameter("Embed",
  37. "If the viewer is being run as an applet, display its output to " +
  38. "an embedded frame in the browser window rather than to a dedicated " +
  39. "window. Embed=1 implies FullScreen=0 and Scale=100.",
  40. false);
  41. public static BoolParameter dotWhenNoCursor
  42. = new BoolParameter("DotWhenNoCursor",
  43. "Show the dot cursor when the server sends an invisible cursor",
  44. false);
  45. public static BoolParameter sendLocalUsername
  46. = new BoolParameter("SendLocalUsername",
  47. "Send the local username for SecurityTypes "+
  48. "such as Plain rather than prompting",
  49. true);
  50. public static StringParameter passwordFile
  51. = new StringParameter("PasswordFile",
  52. "Password file for VNC authentication",
  53. "");
  54. public static AliasParameter passwd
  55. = new AliasParameter("passwd",
  56. "Alias for PasswordFile",
  57. passwordFile);
  58. public static BoolParameter autoSelect
  59. = new BoolParameter("AutoSelect",
  60. "Auto select pixel format and encoding",
  61. true);
  62. public static BoolParameter fullColor
  63. = new BoolParameter("FullColor",
  64. "Use full color - otherwise 6-bit colour is used "+
  65. "until AutoSelect decides the link is fast enough",
  66. true);
  67. public static AliasParameter fullColorAlias
  68. = new AliasParameter("FullColour",
  69. "Alias for FullColor",
  70. Parameters.fullColor);
  71. public static IntParameter lowColorLevel
  72. = new IntParameter("LowColorLevel",
  73. "Color level to use on slow connections. "+
  74. "0 = Very Low (8 colors), 1 = Low (64 colors), "+
  75. "2 = Medium (256 colors)",
  76. 2);
  77. public static AliasParameter lowColorLevelAlias
  78. = new AliasParameter("LowColourLevel",
  79. "Alias for LowColorLevel",
  80. lowColorLevel);
  81. public static StringParameter preferredEncoding
  82. = new StringParameter("PreferredEncoding",
  83. "Preferred encoding to use (Tight, ZRLE, "+
  84. "hextile or raw) - implies AutoSelect=0",
  85. "Tight");
  86. public static BoolParameter remoteResize
  87. = new BoolParameter("RemoteResize",
  88. "Dynamically resize the remote desktop size as "+
  89. "the size of the local client window changes. "+
  90. "(Does not work with all servers)",
  91. true);
  92. public static BoolParameter viewOnly
  93. = new BoolParameter("ViewOnly",
  94. "Don't send any mouse or keyboard events to the server",
  95. false);
  96. public static BoolParameter shared
  97. = new BoolParameter("Shared",
  98. "Don't disconnect other viewers upon "+
  99. "connection - share the desktop instead",
  100. false);
  101. public static BoolParameter maximize
  102. = new BoolParameter("Maximize",
  103. "Maximize viewer window",
  104. false);
  105. public static BoolParameter fullScreen
  106. = new BoolParameter("FullScreen",
  107. "Full Screen Mode",
  108. false);
  109. public static BoolParameter fullScreenAllMonitors
  110. = new BoolParameter("FullScreenAllMonitors",
  111. "Enable full screen over all monitors",
  112. true);
  113. public static BoolParameter acceptClipboard
  114. = new BoolParameter("AcceptClipboard",
  115. "Accept clipboard changes from the server",
  116. true);
  117. public static BoolParameter sendClipboard
  118. = new BoolParameter("SendClipboard",
  119. "Send clipboard changes to the server",
  120. true);
  121. public static IntParameter maxCutText
  122. = new IntParameter("MaxCutText",
  123. "Maximum permitted length of an outgoing clipboard update",
  124. 262144);
  125. public static StringParameter menuKey
  126. = new StringParameter("MenuKey",
  127. "The key which brings up the popup menu",
  128. "F8");
  129. public static StringParameter desktopSize
  130. = new StringParameter("DesktopSize",
  131. "Reconfigure desktop size on the server on connect (if possible)",
  132. "");
  133. public static BoolParameter listenMode
  134. = new BoolParameter("listen",
  135. "Listen for connections from VNC servers",
  136. false);
  137. public static StringParameter scalingFactor
  138. = new StringParameter("ScalingFactor",
  139. "Reduce or enlarge the remote desktop image. "+
  140. "The value is interpreted as a scaling factor "+
  141. "in percent. If the parameter is set to "+
  142. "\"Auto\", then automatic scaling is "+
  143. "performed. Auto-scaling tries to choose a "+
  144. "scaling factor in such a way that the whole "+
  145. "remote desktop will fit on the local screen. "+
  146. "If the parameter is set to \"FixedRatio\", "+
  147. "then automatic scaling is performed, but the "+
  148. "original aspect ratio is preserved.",
  149. "100");
  150. public static BoolParameter alwaysShowServerDialog
  151. = new BoolParameter("AlwaysShowServerDialog",
  152. "Always show the server dialog even if a server has been "+
  153. "specified in an applet parameter or on the command line",
  154. false);
  155. public static StringParameter vncServerName
  156. = new StringParameter("Server",
  157. "The VNC server <host>[:<dpyNum>] or <host>::<port>",
  158. "");
  159. public static BoolParameter acceptBell
  160. = new BoolParameter("AcceptBell",
  161. "Produce a system beep when requested to by the server.",
  162. true);
  163. public static StringParameter via
  164. = new StringParameter("Via",
  165. "Automatically create an encrypted TCP tunnel to "+
  166. "the gateway machine, then connect to the VNC host "+
  167. "through that tunnel. By default, this option invokes "+
  168. "SSH local port forwarding using the embedded JSch "+
  169. "client, however an external SSH client may be specified "+
  170. "using the \"-extSSH\" parameter. Note that when using "+
  171. "the -via option, the VNC host machine name should be "+
  172. "specified from the point of view of the gateway machine, "+
  173. "e.g. \"localhost\" denotes the gateway, "+
  174. "not the machine on which the viewer was launched. "+
  175. "See the System Properties section below for "+
  176. "information on configuring the -Via option.", "");
  177. public static BoolParameter tunnel
  178. = new BoolParameter("Tunnel",
  179. "The -Tunnel command is basically a shorthand for the "+
  180. "-via command when the VNC server and SSH gateway are "+
  181. "one and the same. -Tunnel creates an SSH connection "+
  182. "to the server and forwards the VNC through the tunnel "+
  183. "without the need to specify anything else.", false);
  184. public static BoolParameter extSSH
  185. = new BoolParameter("extSSH",
  186. "By default, SSH tunneling uses the embedded JSch client "+
  187. "for tunnel creation. This option causes the client to "+
  188. "invoke an external SSH client application for all tunneling "+
  189. "operations. By default, \"/usr/bin/ssh\" is used, however "+
  190. "the path to the external application may be specified using "+
  191. "the -SSHClient option.", false);
  192. public static StringParameter extSSHClient
  193. = new StringParameter("extSSHClient",
  194. "Specifies the path to an external SSH client application "+
  195. "that is to be used for tunneling operations when the -extSSH "+
  196. "option is in effect.", "/usr/bin/ssh");
  197. public static StringParameter extSSHArgs
  198. = new StringParameter("extSSHArgs",
  199. "Specifies the arguments string or command template to be used "+
  200. "by the external SSH client application when the -extSSH option "+
  201. "is in effect. The string will be processed according to the same "+
  202. "pattern substitution rules as the VNC_TUNNEL_CMD and VNC_VIA_CMD "+
  203. "system properties, and can be used to override those in a more "+
  204. "command-line friendly way. If not specified, then the appropriate "+
  205. "VNC_TUNNEL_CMD or VNC_VIA_CMD command template will be used.", "");
  206. public static StringParameter sshConfig
  207. = new StringParameter("SSHConfig",
  208. "Specifies the path to an OpenSSH configuration file that to "+
  209. "be parsed by the embedded JSch SSH client during tunneling "+
  210. "operations.", FileUtils.getHomeDir()+".ssh/config");
  211. public static StringParameter sshKey
  212. = new StringParameter("SSHKey",
  213. "When using the Via or Tunnel options with the embedded SSH client, "+
  214. "this parameter specifies the text of the SSH private key to use when "+
  215. "authenticating with the SSH server. You can use \\n within the string "+
  216. "to specify a new line.", "");
  217. public static StringParameter sshKeyFile
  218. = new StringParameter("SSHKeyFile",
  219. "When using the Via or Tunnel options with the embedded SSH client, "+
  220. "this parameter specifies a file that contains an SSH private key "+
  221. "(or keys) to use when authenticating with the SSH server. If not "+
  222. "specified, ~/.ssh/id_dsa or ~/.ssh/id_rsa will be used (if they exist). "+
  223. "Otherwise, the client will fallback to prompting for an SSH password.",
  224. "");
  225. public static StringParameter sshKeyPass
  226. = new StringParameter("SSHKeyPass",
  227. "When using the Via or Tunnel options with the embedded SSH client, "+
  228. "this parameter specifies the passphrase for the SSH key.", "");
  229. public static BoolParameter customCompressLevel
  230. = new BoolParameter("CustomCompressLevel",
  231. "Use custom compression level. Default if CompressLevel is specified.",
  232. false);
  233. public static IntParameter compressLevel
  234. = new IntParameter("CompressLevel",
  235. "Use specified compression level. 0 = Low, 6 = High",
  236. 1);
  237. public static BoolParameter noJpeg
  238. = new BoolParameter("NoJPEG",
  239. "Disable lossy JPEG compression in Tight encoding.",
  240. false);
  241. public static IntParameter qualityLevel
  242. = new IntParameter("QualityLevel",
  243. "JPEG quality level. 0 = Low, 9 = High",
  244. 8);
  245. private static final String IDENTIFIER_STRING
  246. = "TigerVNC Configuration file Version 1.0";
  247. static VoidParameter[] parameterArray = {
  248. CSecurityTLS.X509CA,
  249. CSecurityTLS.X509CRL,
  250. SecurityClient.secTypes,
  251. dotWhenNoCursor,
  252. autoSelect,
  253. fullColor,
  254. lowColorLevel,
  255. preferredEncoding,
  256. customCompressLevel,
  257. compressLevel,
  258. noJpeg,
  259. qualityLevel,
  260. maximize,
  261. fullScreen,
  262. fullScreenAllMonitors,
  263. desktopSize,
  264. remoteResize,
  265. viewOnly,
  266. shared,
  267. acceptClipboard,
  268. sendClipboard,
  269. menuKey,
  270. noLionFS,
  271. sendLocalUsername,
  272. maxCutText,
  273. scalingFactor,
  274. acceptBell,
  275. via,
  276. tunnel,
  277. extSSH,
  278. extSSHClient,
  279. extSSHArgs,
  280. sshConfig,
  281. sshKeyFile,
  282. };
  283. static LogWriter vlog = new LogWriter("Parameters");
  284. public static void saveViewerParameters(String filename, String servername) {
  285. // Write to the registry or a predefined file if no filename was specified.
  286. String filepath;
  287. if (filename == null || filename.isEmpty()) {
  288. saveToReg(servername);
  289. return;
  290. /*
  291. String homeDir = FileUtils.getVncHomeDir();
  292. if (homeDir == null)
  293. throw new Exception("Failed to read configuration file, "+
  294. "can't obtain home directory path.");
  295. filepath = homeDir.concat("default.tigervnc");
  296. */
  297. } else {
  298. filepath = filename;
  299. }
  300. /* Write parameters to file */
  301. File f = new File(filepath);
  302. if (f.exists() && !f.canWrite())
  303. throw new Exception(String.format("Failed to write configuration file,"+
  304. "can't open %s", filepath));
  305. PrintWriter pw = null;
  306. try {
  307. pw = new PrintWriter(f, "UTF-8");
  308. } catch (java.lang.Exception e) {
  309. throw new Exception(e.getMessage());
  310. }
  311. pw.println(IDENTIFIER_STRING);
  312. pw.println("");
  313. if (servername != null && !servername.isEmpty()) {
  314. pw.println(String.format("ServerName=%s\n", servername));
  315. updateConnHistory(servername);
  316. }
  317. for (int i = 0; i < parameterArray.length; i++) {
  318. if (parameterArray[i] instanceof StringParameter) {
  319. //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName()))
  320. pw.println(String.format("%s=%s",parameterArray[i].getName(),
  321. parameterArray[i].getValueStr()));
  322. } else if (parameterArray[i] instanceof IntParameter) {
  323. //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName()))
  324. pw.println(String.format("%s=%s",parameterArray[i].getName(),
  325. parameterArray[i].getValueStr()));
  326. } else if (parameterArray[i] instanceof BoolParameter) {
  327. //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName()))
  328. pw.println(String.format("%s=%s",parameterArray[i].getName(),
  329. parameterArray[i].getValueStr()));
  330. } else {
  331. vlog.error(String.format("Unknown parameter type for parameter %s",
  332. parameterArray[i].getName()));
  333. }
  334. }
  335. pw.flush();
  336. pw.close();
  337. }
  338. public static String loadViewerParameters(String filename) throws Exception {
  339. String servername = "";
  340. String filepath;
  341. if (filename == null) {
  342. return loadFromReg();
  343. /*
  344. String homeDir = FileUtils.getVncHomeDir();
  345. if (homeDir == null)
  346. throw new Exception("Failed to read configuration file, "+
  347. "can't obtain home directory path.");
  348. filepath = homeDir.concat("default.tigervnc");
  349. */
  350. } else {
  351. filepath = filename;
  352. }
  353. /* Read parameters from file */
  354. File f = new File(filepath);
  355. if (!f.exists() || !f.canRead()) {
  356. if (filename == null || filename.isEmpty())
  357. return null;
  358. throw new Exception(String.format("Failed to read configuration file, can't open %s",
  359. filepath));
  360. }
  361. String line = "";
  362. LineNumberReader reader;
  363. try {
  364. reader = new LineNumberReader(new FileReader(f));
  365. } catch (FileNotFoundException e) {
  366. throw new Exception(e.getMessage());
  367. }
  368. int lineNr = 0;
  369. while (line != null) {
  370. // Read the next line
  371. try {
  372. line = reader.readLine();
  373. lineNr = reader.getLineNumber();
  374. if (line == null)
  375. break;
  376. } catch (IOException e) {
  377. throw new Exception(String.format("Failed to read line %d in file %s: %s",
  378. lineNr, filepath, e.getMessage()));
  379. }
  380. // Make sure that the first line of the file has the file identifier string
  381. if(lineNr == 1) {
  382. if(line.equals(IDENTIFIER_STRING))
  383. continue;
  384. else
  385. throw new Exception(String.format("Configuration file %s is in an invalid format", filename));
  386. }
  387. // Skip empty lines and comments
  388. if (line.trim().isEmpty() || line.trim().startsWith("#"))
  389. continue;
  390. // Find the parameter value
  391. int idx = line.indexOf("=");
  392. if (idx == -1) {
  393. vlog.error(String.format("Failed to read line %d in file %s: %s",
  394. lineNr, filename, "Invalid format"));
  395. continue;
  396. }
  397. String value = line.substring(idx+1).trim();
  398. boolean invalidParameterName = true; // Will be set to false below if
  399. // the line contains a valid name.
  400. if (line.substring(0,idx).trim().equalsIgnoreCase("ServerName")) {
  401. if (value.length() > 256) {
  402. vlog.error(String.format("Failed to read line %d in file %s: %s",
  403. lineNr, filepath, "Invalid format or too large value"));
  404. continue;
  405. }
  406. servername = value;
  407. invalidParameterName = false;
  408. } else {
  409. for (int i = 0; i < parameterArray.length; i++) {
  410. if (parameterArray[i] instanceof StringParameter) {
  411. if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) {
  412. if (value.length() > 256) {
  413. vlog.error(String.format("Failed to read line %d in file %s: %s",
  414. lineNr, filepath, "Invalid format or too large value"));
  415. continue;
  416. }
  417. ((StringParameter)parameterArray[i]).setParam(value);
  418. invalidParameterName = false;
  419. }
  420. } else if (parameterArray[i] instanceof IntParameter) {
  421. if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) {
  422. ((IntParameter)parameterArray[i]).setParam(value);
  423. invalidParameterName = false;
  424. }
  425. } else if (parameterArray[i] instanceof BoolParameter) {
  426. if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) {
  427. ((BoolParameter)parameterArray[i]).setParam(value);
  428. invalidParameterName = false;
  429. }
  430. } else {
  431. vlog.error(String.format("Unknown parameter type for parameter %s",
  432. parameterArray[i].getName()));
  433. }
  434. }
  435. }
  436. if (invalidParameterName)
  437. vlog.info(String.format("Unknown parameter %s on line %d in file %s",
  438. line, lineNr, filepath));
  439. }
  440. try {
  441. reader.close();
  442. } catch (IOException e) {
  443. vlog.info(e.getMessage());
  444. } finally {
  445. try {
  446. if (reader != null)
  447. reader.close();
  448. } catch (IOException e) { }
  449. }
  450. return servername;
  451. }
  452. public static void saveToReg(String servername) {
  453. String hKey = "global";
  454. if (servername != null && !servername.isEmpty()) {
  455. UserPreferences.set(hKey, "ServerName", servername);
  456. updateConnHistory(servername);
  457. }
  458. for (int i = 0; i < parameterArray.length; i++) {
  459. if (parameterArray[i] instanceof StringParameter) {
  460. UserPreferences.set(hKey, parameterArray[i].getName(),
  461. parameterArray[i].getValueStr());
  462. } else if (parameterArray[i] instanceof IntParameter) {
  463. UserPreferences.set(hKey, parameterArray[i].getName(),
  464. ((IntParameter)parameterArray[i]).getValue());
  465. } else if (parameterArray[i] instanceof BoolParameter) {
  466. UserPreferences.set(hKey, parameterArray[i].getName(),
  467. ((BoolParameter)parameterArray[i]).getValue());
  468. } else {
  469. vlog.error(String.format("Unknown parameter type for parameter %s",
  470. parameterArray[i].getName()));
  471. }
  472. }
  473. UserPreferences.save(hKey);
  474. }
  475. public static String loadFromReg() {
  476. String hKey = "global";
  477. String servername = UserPreferences.get(hKey, "ServerName");
  478. if (servername == null)
  479. servername = "";
  480. for (int i = 0; i < parameterArray.length; i++) {
  481. if (parameterArray[i] instanceof StringParameter) {
  482. if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) {
  483. String stringValue =
  484. UserPreferences.get(hKey, parameterArray[i].getName());
  485. ((StringParameter)parameterArray[i]).setParam(stringValue);
  486. }
  487. } else if (parameterArray[i] instanceof IntParameter) {
  488. if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) {
  489. int intValue =
  490. UserPreferences.getInt(hKey, parameterArray[i].getName());
  491. ((IntParameter)parameterArray[i]).setParam(intValue);
  492. }
  493. } else if (parameterArray[i] instanceof BoolParameter) {
  494. if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) {
  495. boolean booleanValue =
  496. UserPreferences.getBool(hKey, parameterArray[i].getName());
  497. ((BoolParameter)parameterArray[i]).setParam(booleanValue);
  498. }
  499. } else {
  500. vlog.error(String.format("Unknown parameter type for parameter %s",
  501. parameterArray[i].getName()));
  502. }
  503. }
  504. return servername;
  505. }
  506. public static String loadAppletParameters(VncViewer applet) {
  507. String servername = applet.getParameter("Server");
  508. String serverport = applet.getParameter("Port");
  509. String embedParam = applet.getParameter("Embed");
  510. if (servername == null)
  511. servername = applet.getCodeBase().getHost();
  512. if (serverport != null)
  513. servername = servername.concat("::"+serverport);
  514. else
  515. servername = servername.concat("::5900");
  516. if (embedParam != null)
  517. embed.setParam(embedParam);
  518. for (int i = 0; i < parameterArray.length; i++) {
  519. String value = applet.getParameter(parameterArray[i].getName());
  520. if (value == null)
  521. continue;
  522. if (parameterArray[i] instanceof StringParameter) {
  523. if (value.length() > 256) {
  524. vlog.error(String.format("Failed to read applet parameter %s: %s",
  525. parameterArray[i].getName(),
  526. "Invalid format or too large value"));
  527. continue;
  528. }
  529. ((StringParameter)parameterArray[i]).setParam(value);
  530. } else if (parameterArray[i] instanceof IntParameter) {
  531. ((IntParameter)parameterArray[i]).setParam(value);
  532. } else if (parameterArray[i] instanceof BoolParameter) {
  533. ((BoolParameter)parameterArray[i]).setParam(value);
  534. } else {
  535. vlog.error(String.format("Unknown parameter type for parameter %s",
  536. parameterArray[i].getName()));
  537. }
  538. }
  539. return servername;
  540. }
  541. private static void updateConnHistory(String serverName) {
  542. String hKey = "ServerDialog";
  543. if (serverName != null && !serverName.isEmpty()) {
  544. String valueStr = UserPreferences.get(hKey, "history");
  545. String t = (valueStr == null) ? "" : valueStr;
  546. StringTokenizer st = new StringTokenizer(t, ",");
  547. StringBuffer sb = new StringBuffer().append(serverName);
  548. while (st.hasMoreTokens()) {
  549. String str = st.nextToken();
  550. if (!str.equals(serverName) && !str.equals(""))
  551. sb.append(',').append(str);
  552. }
  553. UserPreferences.set(hKey, "history", sb.toString());
  554. UserPreferences.save(hKey);
  555. }
  556. }
  557. }