| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.tools;
|
|---|
| 3 |
|
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
|---|
| 5 |
|
|---|
| 6 | import java.awt.Desktop;
|
|---|
| 7 | import java.awt.Dimension;
|
|---|
| 8 | import java.awt.GraphicsEnvironment;
|
|---|
| 9 | import java.awt.event.KeyEvent;
|
|---|
| 10 | import java.io.BufferedReader;
|
|---|
| 11 | import java.io.BufferedWriter;
|
|---|
| 12 | import java.io.File;
|
|---|
| 13 | import java.io.FileInputStream;
|
|---|
| 14 | import java.io.IOException;
|
|---|
| 15 | import java.io.InputStreamReader;
|
|---|
| 16 | import java.io.OutputStream;
|
|---|
| 17 | import java.io.OutputStreamWriter;
|
|---|
| 18 | import java.io.Writer;
|
|---|
| 19 | import java.net.URI;
|
|---|
| 20 | import java.net.URISyntaxException;
|
|---|
| 21 | import java.nio.charset.StandardCharsets;
|
|---|
| 22 | import java.nio.file.FileSystems;
|
|---|
| 23 | import java.nio.file.Files;
|
|---|
| 24 | import java.nio.file.Path;
|
|---|
| 25 | import java.nio.file.Paths;
|
|---|
| 26 | import java.security.KeyStore;
|
|---|
| 27 | import java.security.KeyStoreException;
|
|---|
| 28 | import java.security.NoSuchAlgorithmException;
|
|---|
| 29 | import java.security.cert.CertificateException;
|
|---|
| 30 | import java.util.ArrayList;
|
|---|
| 31 | import java.util.Arrays;
|
|---|
| 32 | import java.util.Collection;
|
|---|
| 33 | import java.util.List;
|
|---|
| 34 | import java.util.Locale;
|
|---|
| 35 | import java.util.Properties;
|
|---|
| 36 |
|
|---|
| 37 | import javax.swing.JOptionPane;
|
|---|
| 38 |
|
|---|
| 39 | import org.openstreetmap.josm.Main;
|
|---|
| 40 | import org.openstreetmap.josm.data.Preferences.pref;
|
|---|
| 41 | import org.openstreetmap.josm.data.Preferences.writeExplicitly;
|
|---|
| 42 | import org.openstreetmap.josm.gui.ExtendedDialog;
|
|---|
| 43 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
|---|
| 44 |
|
|---|
| 45 | /**
|
|---|
| 46 | * {@code PlatformHook} base implementation.
|
|---|
| 47 | *
|
|---|
| 48 | * Don't write (Main.platform instanceof PlatformHookUnixoid) because other platform
|
|---|
| 49 | * hooks are subclasses of this class.
|
|---|
| 50 | */
|
|---|
| 51 | public class PlatformHookUnixoid implements PlatformHook {
|
|---|
| 52 |
|
|---|
| 53 | /**
|
|---|
| 54 | * Simple data class to hold information about a font.
|
|---|
| 55 | *
|
|---|
| 56 | * Used for fontconfig.properties files.
|
|---|
| 57 | */
|
|---|
| 58 | public static class FontEntry {
|
|---|
| 59 | /**
|
|---|
| 60 | * The character subset. Basically a free identifier, but should be unique.
|
|---|
| 61 | */
|
|---|
| 62 | @pref
|
|---|
| 63 | public String charset;
|
|---|
| 64 |
|
|---|
| 65 | /**
|
|---|
| 66 | * Platform font name.
|
|---|
| 67 | */
|
|---|
| 68 | @pref @writeExplicitly
|
|---|
| 69 | public String name = "";
|
|---|
| 70 |
|
|---|
| 71 | /**
|
|---|
| 72 | * File name.
|
|---|
| 73 | */
|
|---|
| 74 | @pref @writeExplicitly
|
|---|
| 75 | public String file = "";
|
|---|
| 76 |
|
|---|
| 77 | /**
|
|---|
| 78 | * Constructs a new {@code FontEntry}.
|
|---|
| 79 | */
|
|---|
| 80 | public FontEntry() {
|
|---|
| 81 | }
|
|---|
| 82 |
|
|---|
| 83 | /**
|
|---|
| 84 | * Constructs a new {@code FontEntry}.
|
|---|
| 85 | * @param charset The character subset. Basically a free identifier, but should be unique
|
|---|
| 86 | * @param name Platform font name
|
|---|
| 87 | * @param file File name
|
|---|
| 88 | */
|
|---|
| 89 | public FontEntry(String charset, String name, String file) {
|
|---|
| 90 | this.charset = charset;
|
|---|
| 91 | this.name = name;
|
|---|
| 92 | this.file = file;
|
|---|
| 93 | }
|
|---|
| 94 | }
|
|---|
| 95 |
|
|---|
| 96 | private String osDescription;
|
|---|
| 97 |
|
|---|
| 98 | @Override
|
|---|
| 99 | public void preStartupHook() {
|
|---|
| 100 | }
|
|---|
| 101 |
|
|---|
| 102 | @Override
|
|---|
| 103 | public void afterPrefStartupHook() {
|
|---|
| 104 | }
|
|---|
| 105 |
|
|---|
| 106 | @Override
|
|---|
| 107 | public void startupHook() {
|
|---|
| 108 | }
|
|---|
| 109 |
|
|---|
| 110 | @Override
|
|---|
| 111 | public void openUrl(String url) throws IOException {
|
|---|
| 112 | for (String program : Main.pref.getCollection("browser.unix",
|
|---|
| 113 | Arrays.asList("xdg-open", "#DESKTOP#", "$BROWSER", "gnome-open", "kfmclient openURL", "firefox"))) {
|
|---|
| 114 | try {
|
|---|
| 115 | if ("#DESKTOP#".equals(program)) {
|
|---|
| 116 | Desktop.getDesktop().browse(new URI(url));
|
|---|
| 117 | } else if (program.startsWith("$")) {
|
|---|
| 118 | program = System.getenv().get(program.substring(1));
|
|---|
| 119 | Runtime.getRuntime().exec(new String[]{program, url});
|
|---|
| 120 | } else {
|
|---|
| 121 | Runtime.getRuntime().exec(new String[]{program, url});
|
|---|
| 122 | }
|
|---|
| 123 | return;
|
|---|
| 124 | } catch (IOException | URISyntaxException e) {
|
|---|
| 125 | Main.warn(e);
|
|---|
| 126 | }
|
|---|
| 127 | }
|
|---|
| 128 | }
|
|---|
| 129 |
|
|---|
| 130 | @Override
|
|---|
| 131 | public void initSystemShortcuts() {
|
|---|
| 132 | // CHECKSTYLE.OFF: LineLength
|
|---|
| 133 | // TODO: Insert system shortcuts here. See Windows and especially OSX to see how to.
|
|---|
| 134 | for (int i = KeyEvent.VK_F1; i <= KeyEvent.VK_F12; ++i) {
|
|---|
| 135 | Shortcut.registerSystemShortcut("screen:toogle"+i, tr("reserved"), i, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
|
|---|
| 136 | .setAutomatic();
|
|---|
| 137 | }
|
|---|
| 138 | Shortcut.registerSystemShortcut("system:reset", tr("reserved"), KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
|
|---|
| 139 | .setAutomatic();
|
|---|
| 140 | Shortcut.registerSystemShortcut("system:resetX", tr("reserved"), KeyEvent.VK_BACK_SPACE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
|
|---|
| 141 | .setAutomatic();
|
|---|
| 142 | // CHECKSTYLE.ON: LineLength
|
|---|
| 143 | }
|
|---|
| 144 |
|
|---|
| 145 | /**
|
|---|
| 146 | * This should work for all platforms. Yeah, should.
|
|---|
| 147 | * See PlatformHook.java for a list of reasons why this is implemented here...
|
|---|
| 148 | */
|
|---|
| 149 | @Override
|
|---|
| 150 | public String makeTooltip(String name, Shortcut sc) {
|
|---|
| 151 | StringBuilder result = new StringBuilder();
|
|---|
| 152 | result.append("<html>").append(name);
|
|---|
| 153 | if (sc != null && !sc.getKeyText().isEmpty()) {
|
|---|
| 154 | result.append(' ')
|
|---|
| 155 | .append("<font size='-2'>")
|
|---|
| 156 | .append('(').append(sc.getKeyText()).append(')')
|
|---|
| 157 | .append("</font>");
|
|---|
| 158 | }
|
|---|
| 159 | return result.append(" </html>").toString();
|
|---|
| 160 | }
|
|---|
| 161 |
|
|---|
| 162 | @Override
|
|---|
| 163 | public String getDefaultStyle() {
|
|---|
| 164 | return "javax.swing.plaf.metal.MetalLookAndFeel";
|
|---|
| 165 | }
|
|---|
| 166 |
|
|---|
| 167 | @Override
|
|---|
| 168 | public boolean canFullscreen() {
|
|---|
| 169 | return !GraphicsEnvironment.isHeadless() &&
|
|---|
| 170 | GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().isFullScreenSupported();
|
|---|
| 171 | }
|
|---|
| 172 |
|
|---|
| 173 | @Override
|
|---|
| 174 | public boolean rename(File from, File to) {
|
|---|
| 175 | return from.renameTo(to);
|
|---|
| 176 | }
|
|---|
| 177 |
|
|---|
| 178 | /**
|
|---|
| 179 | * Determines if the JVM is OpenJDK-based.
|
|---|
| 180 | * @return {@code true} if {@code java.home} contains "openjdk", {@code false} otherwise
|
|---|
| 181 | * @since 6951
|
|---|
| 182 | */
|
|---|
| 183 | public static boolean isOpenJDK() {
|
|---|
| 184 | String javaHome = System.getProperty("java.home");
|
|---|
| 185 | return javaHome != null && javaHome.contains("openjdk");
|
|---|
| 186 | }
|
|---|
| 187 |
|
|---|
| 188 | /**
|
|---|
| 189 | * Get the package name including detailed version.
|
|---|
| 190 | * @param packageNames The possible package names (when a package can have different names on different distributions)
|
|---|
| 191 | * @return The package name and package version if it can be identified, null otherwise
|
|---|
| 192 | * @since 7314
|
|---|
| 193 | */
|
|---|
| 194 | public static String getPackageDetails(String ... packageNames) {
|
|---|
| 195 | try {
|
|---|
| 196 | boolean dpkg = Files.exists(Paths.get("/usr/bin/dpkg-query"));
|
|---|
| 197 | boolean eque = Files.exists(Paths.get("/usr/bin/equery"));
|
|---|
| 198 | boolean rpm = Files.exists(Paths.get("/bin/rpm"));
|
|---|
| 199 | if (dpkg || rpm || eque) {
|
|---|
| 200 | for (String packageName : packageNames) {
|
|---|
| 201 | String[] args = null;
|
|---|
| 202 | if (dpkg) {
|
|---|
| 203 | args = new String[] {"dpkg-query", "--show", "--showformat", "${Architecture}-${Version}", packageName};
|
|---|
| 204 | } else if (eque) {
|
|---|
| 205 | args = new String[] {"equery", "-q", "list", "-e", "--format=$fullversion", packageName};
|
|---|
| 206 | } else {
|
|---|
| 207 | args = new String[] {"rpm", "-q", "--qf", "%{arch}-%{version}", packageName};
|
|---|
| 208 | }
|
|---|
| 209 | String version = Utils.execOutput(Arrays.asList(args));
|
|---|
| 210 | if (version != null && !version.contains("not installed")) {
|
|---|
| 211 | return packageName + ':' + version;
|
|---|
| 212 | }
|
|---|
| 213 | }
|
|---|
| 214 | }
|
|---|
| 215 | } catch (IOException e) {
|
|---|
| 216 | Main.warn(e);
|
|---|
| 217 | }
|
|---|
| 218 | return null;
|
|---|
| 219 | }
|
|---|
| 220 |
|
|---|
| 221 | /**
|
|---|
| 222 | * Get the Java package name including detailed version.
|
|---|
| 223 | *
|
|---|
| 224 | * Some Java bugs are specific to a certain security update, so in addition
|
|---|
| 225 | * to the Java version, we also need the exact package version.
|
|---|
| 226 | *
|
|---|
| 227 | * @return The package name and package version if it can be identified, null otherwise
|
|---|
| 228 | */
|
|---|
| 229 | public String getJavaPackageDetails() {
|
|---|
| 230 | String home = System.getProperty("java.home");
|
|---|
| 231 | if (home.contains("java-7-openjdk") || home.contains("java-1.7.0-openjdk")) {
|
|---|
| 232 | return getPackageDetails("openjdk-7-jre", "java-1_7_0-openjdk", "java-1.7.0-openjdk");
|
|---|
| 233 | } else if (home.contains("icedtea")) {
|
|---|
| 234 | return getPackageDetails("icedtea-bin");
|
|---|
| 235 | } else if (home.contains("oracle")) {
|
|---|
| 236 | return getPackageDetails("oracle-jdk-bin", "oracle-jre-bin");
|
|---|
| 237 | }
|
|---|
| 238 | return null;
|
|---|
| 239 | }
|
|---|
| 240 |
|
|---|
| 241 | /**
|
|---|
| 242 | * Get the Web Start package name including detailed version.
|
|---|
| 243 | *
|
|---|
| 244 | * OpenJDK packages are shipped with icedtea-web package,
|
|---|
| 245 | * but its version generally does not match main java package version.
|
|---|
| 246 | *
|
|---|
| 247 | * Simply return {@code null} if there's no separate package for Java WebStart.
|
|---|
| 248 | *
|
|---|
| 249 | * @return The package name and package version if it can be identified, null otherwise
|
|---|
| 250 | */
|
|---|
| 251 | public String getWebStartPackageDetails() {
|
|---|
| 252 | if (isOpenJDK()) {
|
|---|
| 253 | return getPackageDetails("icedtea-netx", "icedtea-web");
|
|---|
| 254 | }
|
|---|
| 255 | return null;
|
|---|
| 256 | }
|
|---|
| 257 |
|
|---|
| 258 | protected String buildOSDescription() {
|
|---|
| 259 | String osName = System.getProperty("os.name");
|
|---|
| 260 | if ("Linux".equalsIgnoreCase(osName)) {
|
|---|
| 261 | try {
|
|---|
| 262 | // Try lsb_release (only available on LSB-compliant Linux systems,
|
|---|
| 263 | // see https://www.linuxbase.org/lsb-cert/productdir.php?by_prod )
|
|---|
| 264 | Process p = Runtime.getRuntime().exec("lsb_release -ds");
|
|---|
| 265 | try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
|
|---|
| 266 | String line = Utils.strip(input.readLine());
|
|---|
| 267 | if (line != null && !line.isEmpty()) {
|
|---|
| 268 | line = line.replaceAll("\"+", "");
|
|---|
| 269 | line = line.replaceAll("NAME=", ""); // strange code for some Gentoo's
|
|---|
| 270 | if (line.startsWith("Linux ")) // e.g. Linux Mint
|
|---|
| 271 | return line;
|
|---|
| 272 | else if (!line.isEmpty())
|
|---|
| 273 | return "Linux " + line;
|
|---|
| 274 | }
|
|---|
| 275 | }
|
|---|
| 276 | } catch (IOException e) {
|
|---|
| 277 | // Non LSB-compliant Linux system. List of common fallback release files: http://linuxmafia.com/faq/Admin/release-files.html
|
|---|
| 278 | for (LinuxReleaseInfo info : new LinuxReleaseInfo[]{
|
|---|
| 279 | new LinuxReleaseInfo("/etc/lsb-release", "DISTRIB_DESCRIPTION", "DISTRIB_ID", "DISTRIB_RELEASE"),
|
|---|
| 280 | new LinuxReleaseInfo("/etc/os-release", "PRETTY_NAME", "NAME", "VERSION"),
|
|---|
| 281 | new LinuxReleaseInfo("/etc/arch-release"),
|
|---|
| 282 | new LinuxReleaseInfo("/etc/debian_version", "Debian GNU/Linux "),
|
|---|
| 283 | new LinuxReleaseInfo("/etc/fedora-release"),
|
|---|
| 284 | new LinuxReleaseInfo("/etc/gentoo-release"),
|
|---|
| 285 | new LinuxReleaseInfo("/etc/redhat-release"),
|
|---|
| 286 | new LinuxReleaseInfo("/etc/SuSE-release")
|
|---|
| 287 | }) {
|
|---|
| 288 | String description = info.extractDescription();
|
|---|
| 289 | if (description != null && !description.isEmpty()) {
|
|---|
| 290 | return "Linux " + description;
|
|---|
| 291 | }
|
|---|
| 292 | }
|
|---|
| 293 | }
|
|---|
| 294 | }
|
|---|
| 295 | return osName;
|
|---|
| 296 | }
|
|---|
| 297 |
|
|---|
| 298 | @Override
|
|---|
| 299 | public String getOSDescription() {
|
|---|
| 300 | if (osDescription == null) {
|
|---|
| 301 | osDescription = buildOSDescription();
|
|---|
| 302 | }
|
|---|
| 303 | return osDescription;
|
|---|
| 304 | }
|
|---|
| 305 |
|
|---|
| 306 | protected static class LinuxReleaseInfo {
|
|---|
| 307 | private final String path;
|
|---|
| 308 | private final String descriptionField;
|
|---|
| 309 | private final String idField;
|
|---|
| 310 | private final String releaseField;
|
|---|
| 311 | private final boolean plainText;
|
|---|
| 312 | private final String prefix;
|
|---|
| 313 |
|
|---|
| 314 | public LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField) {
|
|---|
| 315 | this(path, descriptionField, idField, releaseField, false, null);
|
|---|
| 316 | }
|
|---|
| 317 |
|
|---|
| 318 | public LinuxReleaseInfo(String path) {
|
|---|
| 319 | this(path, null, null, null, true, null);
|
|---|
| 320 | }
|
|---|
| 321 |
|
|---|
| 322 | public LinuxReleaseInfo(String path, String prefix) {
|
|---|
| 323 | this(path, null, null, null, true, prefix);
|
|---|
| 324 | }
|
|---|
| 325 |
|
|---|
| 326 | private LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField, boolean plainText, String prefix) {
|
|---|
| 327 | this.path = path;
|
|---|
| 328 | this.descriptionField = descriptionField;
|
|---|
| 329 | this.idField = idField;
|
|---|
| 330 | this.releaseField = releaseField;
|
|---|
| 331 | this.plainText = plainText;
|
|---|
| 332 | this.prefix = prefix;
|
|---|
| 333 | }
|
|---|
| 334 |
|
|---|
| 335 | @Override public String toString() {
|
|---|
| 336 | return "ReleaseInfo [path=" + path + ", descriptionField=" + descriptionField +
|
|---|
| 337 | ", idField=" + idField + ", releaseField=" + releaseField + ']';
|
|---|
| 338 | }
|
|---|
| 339 |
|
|---|
| 340 | /**
|
|---|
| 341 | * Extracts OS detailed information from a Linux release file (/etc/xxx-release)
|
|---|
| 342 | * @return The OS detailed information, or {@code null}
|
|---|
| 343 | */
|
|---|
| 344 | public String extractDescription() {
|
|---|
| 345 | String result = null;
|
|---|
| 346 | if (path != null) {
|
|---|
| 347 | Path p = Paths.get(path);
|
|---|
| 348 | if (Files.exists(p)) {
|
|---|
| 349 | try (BufferedReader reader = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
|
|---|
| 350 | String id = null;
|
|---|
| 351 | String release = null;
|
|---|
| 352 | String line;
|
|---|
| 353 | while (result == null && (line = reader.readLine()) != null) {
|
|---|
| 354 | if (line.contains("=")) {
|
|---|
| 355 | String[] tokens = line.split("=");
|
|---|
| 356 | if (tokens.length >= 2) {
|
|---|
| 357 | // Description, if available, contains exactly what we need
|
|---|
| 358 | if (descriptionField != null && descriptionField.equalsIgnoreCase(tokens[0])) {
|
|---|
| 359 | result = Utils.strip(tokens[1]);
|
|---|
| 360 | } else if (idField != null && idField.equalsIgnoreCase(tokens[0])) {
|
|---|
| 361 | id = Utils.strip(tokens[1]);
|
|---|
| 362 | } else if (releaseField != null && releaseField.equalsIgnoreCase(tokens[0])) {
|
|---|
| 363 | release = Utils.strip(tokens[1]);
|
|---|
| 364 | }
|
|---|
| 365 | }
|
|---|
| 366 | } else if (plainText && !line.isEmpty()) {
|
|---|
| 367 | // Files composed of a single line
|
|---|
| 368 | result = Utils.strip(line);
|
|---|
| 369 | }
|
|---|
| 370 | }
|
|---|
| 371 | // If no description has been found, try to rebuild it with "id" + "release" (i.e. "name" + "version")
|
|---|
| 372 | if (result == null && id != null && release != null) {
|
|---|
| 373 | result = id + ' ' + release;
|
|---|
| 374 | }
|
|---|
| 375 | } catch (IOException e) {
|
|---|
| 376 | // Ignore
|
|---|
| 377 | if (Main.isTraceEnabled()) {
|
|---|
| 378 | Main.trace(e.getMessage());
|
|---|
| 379 | }
|
|---|
| 380 | }
|
|---|
| 381 | }
|
|---|
| 382 | }
|
|---|
| 383 | // Append prefix if any
|
|---|
| 384 | if (result != null && !result.isEmpty() && prefix != null && !prefix.isEmpty()) {
|
|---|
| 385 | result = prefix + result;
|
|---|
| 386 | }
|
|---|
| 387 | if (result != null)
|
|---|
| 388 | result = result.replaceAll("\"+", "");
|
|---|
| 389 | return result;
|
|---|
| 390 | }
|
|---|
| 391 | }
|
|---|
| 392 |
|
|---|
| 393 | protected void askUpdateJava(String version) {
|
|---|
| 394 | askUpdateJava(version, "https://www.java.com/download");
|
|---|
| 395 | }
|
|---|
| 396 |
|
|---|
| 397 | // Method kept because strings have already been translated. To enable for Java 8 migration somewhere in 2016
|
|---|
| 398 | protected void askUpdateJava(final String version, final String url) {
|
|---|
| 399 | GuiHelper.runInEDTAndWait(new Runnable() {
|
|---|
| 400 | @Override
|
|---|
| 401 | public void run() {
|
|---|
| 402 | ExtendedDialog ed = new ExtendedDialog(
|
|---|
| 403 | Main.parent,
|
|---|
| 404 | tr("Outdated Java version"),
|
|---|
| 405 | new String[]{tr("Update Java"), tr("Cancel")});
|
|---|
| 406 | // Check if the dialog has not already been permanently hidden by user
|
|---|
| 407 | if (!ed.toggleEnable("askUpdateJava8").toggleCheckState()) {
|
|---|
| 408 | ed.setButtonIcons(new String[]{"java", "cancel"}).setCancelButton(2);
|
|---|
| 409 | ed.setMinimumSize(new Dimension(480, 300));
|
|---|
| 410 | ed.setIcon(JOptionPane.WARNING_MESSAGE);
|
|---|
| 411 | String content = tr("You are running version {0} of Java.", "<b>"+version+"</b>")+"<br><br>";
|
|---|
| 412 | if ("Sun Microsystems Inc.".equals(System.getProperty("java.vendor")) && !isOpenJDK()) {
|
|---|
| 413 | content += "<b>"+tr("This version is no longer supported by {0} since {1} and is not recommended for use.",
|
|---|
| 414 | "Oracle", tr("April 2015"))+"</b><br><br>";
|
|---|
| 415 | }
|
|---|
| 416 | content += "<b>" +
|
|---|
| 417 | tr("JOSM will soon stop working with this version; we highly recommend you to update to Java {0}.", "8")
|
|---|
| 418 | + "</b><br><br>" +
|
|---|
| 419 | tr("Would you like to update now ?");
|
|---|
| 420 | ed.setContent(content);
|
|---|
| 421 |
|
|---|
| 422 | if (ed.showDialog().getValue() == 1) {
|
|---|
| 423 | try {
|
|---|
| 424 | openUrl(url);
|
|---|
| 425 | } catch (IOException e) {
|
|---|
| 426 | Main.warn(e);
|
|---|
| 427 | }
|
|---|
| 428 | }
|
|---|
| 429 | }
|
|---|
| 430 | }
|
|---|
| 431 | });
|
|---|
| 432 | }
|
|---|
| 433 |
|
|---|
| 434 | @Override
|
|---|
| 435 | public boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert)
|
|---|
| 436 | throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
|
|---|
| 437 | // TODO setup HTTPS certificate on Unix systems
|
|---|
| 438 | return false;
|
|---|
| 439 | }
|
|---|
| 440 |
|
|---|
| 441 | @Override
|
|---|
| 442 | public File getDefaultCacheDirectory() {
|
|---|
| 443 | return new File(Main.pref.getUserDataDirectory(), "cache");
|
|---|
| 444 | }
|
|---|
| 445 |
|
|---|
| 446 | @Override
|
|---|
| 447 | public File getDefaultPrefDirectory() {
|
|---|
| 448 | return new File(System.getProperty("user.home"), ".josm");
|
|---|
| 449 | }
|
|---|
| 450 |
|
|---|
| 451 | @Override
|
|---|
| 452 | public File getDefaultUserDataDirectory() {
|
|---|
| 453 | // Use preferences directory by default
|
|---|
| 454 | return Main.pref.getPreferencesDirectory();
|
|---|
| 455 | }
|
|---|
| 456 |
|
|---|
| 457 | /**
|
|---|
| 458 | * <p>Add more fallback fonts to the Java runtime, in order to get
|
|---|
| 459 | * support for more scripts.</p>
|
|---|
| 460 | *
|
|---|
| 461 | * <p>The font configuration in Java doesn't include some Indic scripts,
|
|---|
| 462 | * even though MS Windows ships with fonts that cover these unicode ranges.</p>
|
|---|
| 463 | *
|
|---|
| 464 | * <p>To fix this, the fontconfig.properties template is copied to the JOSM
|
|---|
| 465 | * cache folder. Then, the additional entries are added to the font
|
|---|
| 466 | * configuration. Finally the system property "sun.awt.fontconfig" is set
|
|---|
| 467 | * to the customized fontconfig.properties file.</p>
|
|---|
| 468 | *
|
|---|
| 469 | * <p>This is a crude hack, but better than no font display at all for these languages.
|
|---|
| 470 | * There is no guarantee, that the template file
|
|---|
| 471 | * ($JAVA_HOME/lib/fontconfig.properties.src) matches the default
|
|---|
| 472 | * configuration (which is in a binary format).
|
|---|
| 473 | * Furthermore, the system property "sun.awt.fontconfig" is undocumented and
|
|---|
| 474 | * may no longer work in future versions of Java.</p>
|
|---|
| 475 | *
|
|---|
| 476 | * <p>Related Java bug: <a href="https://bugs.openjdk.java.net/browse/JDK-8008572">JDK-8008572</a></p>
|
|---|
| 477 | *
|
|---|
| 478 | * @param templateFileName file name of the fontconfig.properties template file
|
|---|
| 479 | */
|
|---|
| 480 | protected void extendFontconfig(String templateFileName) {
|
|---|
| 481 | String customFontconfigFile = Main.pref.get("fontconfig.properties", null);
|
|---|
| 482 | if (customFontconfigFile != null) {
|
|---|
| 483 | Utils.updateSystemProperty("sun.awt.fontconfig", customFontconfigFile);
|
|---|
| 484 | return;
|
|---|
| 485 | }
|
|---|
| 486 | if (!Main.pref.getBoolean("font.extended-unicode", true))
|
|---|
| 487 | return;
|
|---|
| 488 |
|
|---|
| 489 | String javaLibPath = System.getProperty("java.home") + File.separator + "lib";
|
|---|
| 490 | Path templateFile = FileSystems.getDefault().getPath(javaLibPath, templateFileName);
|
|---|
| 491 | if (!Files.isReadable(templateFile)) {
|
|---|
| 492 | Main.warn("extended font config - unable to find font config template file "+templateFile.toString());
|
|---|
| 493 | return;
|
|---|
| 494 | }
|
|---|
| 495 | try (FileInputStream fis = new FileInputStream(templateFile.toFile())) {
|
|---|
| 496 | Properties props = new Properties();
|
|---|
| 497 | props.load(fis);
|
|---|
| 498 | byte[] content = Files.readAllBytes(templateFile);
|
|---|
| 499 | File cachePath = Main.pref.getCacheDirectory();
|
|---|
| 500 | Path fontconfigFile = cachePath.toPath().resolve("fontconfig.properties");
|
|---|
| 501 | OutputStream os = Files.newOutputStream(fontconfigFile);
|
|---|
| 502 | os.write(content);
|
|---|
| 503 | try (Writer w = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
|
|---|
| 504 | Collection<FontEntry> extrasPref = Main.pref.getListOfStructs(
|
|---|
| 505 | "font.extended-unicode.extra-items", getAdditionalFonts(), FontEntry.class);
|
|---|
| 506 | Collection<FontEntry> extras = new ArrayList<>();
|
|---|
| 507 | w.append("\n\n# Added by JOSM to extend unicode coverage of Java font support:\n\n");
|
|---|
| 508 | List<String> allCharSubsets = new ArrayList<>();
|
|---|
| 509 | for (FontEntry entry: extrasPref) {
|
|---|
| 510 | Collection<String> fontsAvail = getInstalledFonts();
|
|---|
| 511 | if (fontsAvail != null && fontsAvail.contains(entry.file.toUpperCase(Locale.ENGLISH))) {
|
|---|
| 512 | if (!allCharSubsets.contains(entry.charset)) {
|
|---|
| 513 | allCharSubsets.add(entry.charset);
|
|---|
| 514 | extras.add(entry);
|
|---|
| 515 | } else {
|
|---|
| 516 | Main.trace("extended font config - already registered font for charset ''{0}'' - skipping ''{1}''",
|
|---|
| 517 | entry.charset, entry.name);
|
|---|
| 518 | }
|
|---|
| 519 | } else {
|
|---|
| 520 | Main.trace("extended font config - Font ''{0}'' not found on system - skipping", entry.name);
|
|---|
| 521 | }
|
|---|
| 522 | }
|
|---|
| 523 | for (FontEntry entry: extras) {
|
|---|
| 524 | allCharSubsets.add(entry.charset);
|
|---|
| 525 | if ("".equals(entry.name)) {
|
|---|
| 526 | continue;
|
|---|
| 527 | }
|
|---|
| 528 | String key = "allfonts." + entry.charset;
|
|---|
| 529 | String value = entry.name;
|
|---|
| 530 | String prevValue = props.getProperty(key);
|
|---|
| 531 | if (prevValue != null && !prevValue.equals(value)) {
|
|---|
| 532 | Main.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
|
|---|
| 533 | }
|
|---|
| 534 | w.append(key + '=' + value + '\n');
|
|---|
| 535 | }
|
|---|
| 536 | w.append('\n');
|
|---|
| 537 | for (FontEntry entry: extras) {
|
|---|
| 538 | if ("".equals(entry.name) || "".equals(entry.file)) {
|
|---|
| 539 | continue;
|
|---|
| 540 | }
|
|---|
| 541 | String key = "filename." + entry.name.replace(' ', '_');
|
|---|
| 542 | String value = entry.file;
|
|---|
| 543 | String prevValue = props.getProperty(key);
|
|---|
| 544 | if (prevValue != null && !prevValue.equals(value)) {
|
|---|
| 545 | Main.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
|
|---|
| 546 | }
|
|---|
| 547 | w.append(key + '=' + value + '\n');
|
|---|
| 548 | }
|
|---|
| 549 | w.append('\n');
|
|---|
| 550 | String fallback = props.getProperty("sequence.fallback");
|
|---|
| 551 | if (fallback != null) {
|
|---|
| 552 | w.append("sequence.fallback=" + fallback + ',' + Utils.join(",", allCharSubsets) + '\n');
|
|---|
| 553 | } else {
|
|---|
| 554 | w.append("sequence.fallback=" + Utils.join(",", allCharSubsets) + '\n');
|
|---|
| 555 | }
|
|---|
| 556 | }
|
|---|
| 557 | Utils.updateSystemProperty("sun.awt.fontconfig", fontconfigFile.toString());
|
|---|
| 558 | } catch (IOException ex) {
|
|---|
| 559 | Main.error(ex);
|
|---|
| 560 | }
|
|---|
| 561 | }
|
|---|
| 562 |
|
|---|
| 563 | /**
|
|---|
| 564 | * Get a list of fonts that are installed on the system.
|
|---|
| 565 | *
|
|---|
| 566 | * Must be done without triggering the Java Font initialization.
|
|---|
| 567 | * (See {@link #extendFontconfig(java.lang.String)}, have to set system
|
|---|
| 568 | * property first, which is then read by sun.awt.FontConfiguration upon initialization.)
|
|---|
| 569 | *
|
|---|
| 570 | * @return list of file names
|
|---|
| 571 | */
|
|---|
| 572 | public Collection<String> getInstalledFonts() {
|
|---|
| 573 | throw new UnsupportedOperationException();
|
|---|
| 574 | }
|
|---|
| 575 |
|
|---|
| 576 | /**
|
|---|
| 577 | * Get default list of additional fonts to add to the configuration.
|
|---|
| 578 | *
|
|---|
| 579 | * Java will choose thee first font in the list that can render a certain character.
|
|---|
| 580 | *
|
|---|
| 581 | * @return list of FontEntry objects
|
|---|
| 582 | */
|
|---|
| 583 | public Collection<FontEntry> getAdditionalFonts() {
|
|---|
| 584 | throw new UnsupportedOperationException();
|
|---|
| 585 | }
|
|---|
| 586 | }
|
|---|