001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import static java.awt.event.InputEvent.ALT_DOWN_MASK; 005import static java.awt.event.InputEvent.CTRL_DOWN_MASK; 006import static java.awt.event.InputEvent.SHIFT_DOWN_MASK; 007import static java.awt.event.KeyEvent.VK_A; 008import static java.awt.event.KeyEvent.VK_C; 009import static java.awt.event.KeyEvent.VK_D; 010import static java.awt.event.KeyEvent.VK_DELETE; 011import static java.awt.event.KeyEvent.VK_DOWN; 012import static java.awt.event.KeyEvent.VK_ENTER; 013import static java.awt.event.KeyEvent.VK_ESCAPE; 014import static java.awt.event.KeyEvent.VK_F10; 015import static java.awt.event.KeyEvent.VK_F4; 016import static java.awt.event.KeyEvent.VK_LEFT; 017import static java.awt.event.KeyEvent.VK_NUM_LOCK; 018import static java.awt.event.KeyEvent.VK_PRINTSCREEN; 019import static java.awt.event.KeyEvent.VK_RIGHT; 020import static java.awt.event.KeyEvent.VK_SHIFT; 021import static java.awt.event.KeyEvent.VK_SPACE; 022import static java.awt.event.KeyEvent.VK_TAB; 023import static java.awt.event.KeyEvent.VK_UP; 024import static java.awt.event.KeyEvent.VK_V; 025import static java.awt.event.KeyEvent.VK_X; 026import static java.awt.event.KeyEvent.VK_Y; 027import static java.awt.event.KeyEvent.VK_Z; 028import static org.openstreetmap.josm.tools.I18n.tr; 029import static org.openstreetmap.josm.tools.Utils.getSystemEnv; 030import static org.openstreetmap.josm.tools.Utils.getSystemProperty; 031import static org.openstreetmap.josm.tools.WinRegistry.HKEY_LOCAL_MACHINE; 032 033import java.awt.Desktop; 034import java.io.BufferedWriter; 035import java.io.File; 036import java.io.IOException; 037import java.io.InputStream; 038import java.io.OutputStream; 039import java.io.OutputStreamWriter; 040import java.io.Writer; 041import java.lang.reflect.InvocationTargetException; 042import java.net.URISyntaxException; 043import java.nio.charset.StandardCharsets; 044import java.nio.file.DirectoryIteratorException; 045import java.nio.file.DirectoryStream; 046import java.nio.file.FileSystems; 047import java.nio.file.Files; 048import java.nio.file.InvalidPathException; 049import java.nio.file.Path; 050import java.security.KeyStore; 051import java.security.KeyStoreException; 052import java.security.MessageDigest; 053import java.security.NoSuchAlgorithmException; 054import java.security.cert.Certificate; 055import java.security.cert.CertificateException; 056import java.security.cert.X509Certificate; 057import java.text.ParseException; 058import java.util.ArrayList; 059import java.util.Arrays; 060import java.util.Collection; 061import java.util.Enumeration; 062import java.util.HashSet; 063import java.util.List; 064import java.util.Locale; 065import java.util.Properties; 066import java.util.Set; 067import java.util.concurrent.ExecutionException; 068import java.util.concurrent.TimeUnit; 069import java.util.regex.Matcher; 070import java.util.regex.Pattern; 071 072import org.openstreetmap.josm.data.Preferences; 073import org.openstreetmap.josm.data.StructUtils; 074import org.openstreetmap.josm.data.StructUtils.StructEntry; 075import org.openstreetmap.josm.data.StructUtils.WriteExplicitly; 076import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend; 077import org.openstreetmap.josm.io.NetworkManager; 078import org.openstreetmap.josm.io.OnlineResource; 079import org.openstreetmap.josm.spi.preferences.Config; 080 081/** 082 * {@code PlatformHook} implementation for Microsoft Windows systems. 083 * @since 1023 084 */ 085public class PlatformHookWindows implements PlatformHook { 086 087 /** 088 * Pattern of Microsoft .NET and Powershell version numbers in registry. 089 */ 090 private static final Pattern MS_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+.*)?"); 091 092 /** 093 * Simple data class to hold information about a font. 094 * 095 * Used for fontconfig.properties files. 096 */ 097 public static class FontEntry { 098 /** 099 * The character subset. Basically a free identifier, but should be unique. 100 */ 101 @StructEntry 102 public String charset; 103 104 /** 105 * Platform font name. 106 */ 107 @StructEntry 108 @WriteExplicitly 109 public String name = ""; 110 111 /** 112 * File name. 113 */ 114 @StructEntry 115 @WriteExplicitly 116 public String file = ""; 117 118 /** 119 * Constructs a new {@code FontEntry}. 120 */ 121 public FontEntry() { 122 // Default constructor needed for construction by reflection 123 } 124 125 /** 126 * Constructs a new {@code FontEntry}. 127 * @param charset The character subset. Basically a free identifier, but should be unique 128 * @param name Platform font name 129 * @param file File name 130 */ 131 public FontEntry(String charset, String name, String file) { 132 this.charset = charset; 133 this.name = name; 134 this.file = file; 135 } 136 } 137 138 private static final String WINDOWS_ROOT = "Windows-ROOT"; 139 140 private static final String CURRENT_VERSION = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; 141 142 private String oSBuildNumber; 143 144 @Override 145 public Platform getPlatform() { 146 return Platform.WINDOWS; 147 } 148 149 @Override 150 public void afterPrefStartupHook() { 151 extendFontconfig("fontconfig.properties.src"); 152 } 153 154 @Override 155 public void startupHook(JavaExpirationCallback callback) { 156 checkExpiredJava(callback); 157 } 158 159 @Override 160 public void openUrl(String url) throws IOException { 161 if (!url.startsWith("file:/")) { 162 final String customBrowser = Config.getPref().get("browser.windows", ""); 163 if (!customBrowser.isEmpty()) { 164 Runtime.getRuntime().exec(new String[]{customBrowser, url}); 165 return; 166 } 167 } 168 try { 169 // Desktop API works fine under Windows 170 Desktop.getDesktop().browse(Utils.urlToURI(url)); 171 } catch (IOException | URISyntaxException e) { 172 Logging.log(Logging.LEVEL_WARN, "Desktop class failed. Platform dependent fall back for open url in browser.", e); 173 Runtime.getRuntime().exec(new String[]{"rundll32", "url.dll,FileProtocolHandler", url}); 174 } 175 } 176 177 @Override 178 public void initSystemShortcuts() { 179 // CHECKSTYLE.OFF: LineLength 180 //Shortcut.registerSystemCut("system:menuexit", tr("reserved"), VK_Q, CTRL_DOWN_MASK); 181 Shortcut.registerSystemShortcut("system:duplicate", tr("reserved"), VK_D, CTRL_DOWN_MASK); // not really system, but to avoid odd results 182 183 // Windows 7 shortcuts: http://windows.microsoft.com/en-US/windows7/Keyboard-shortcuts 184 185 // Shortcuts with setAutomatic(): items with automatic shortcuts will not be added to the menu bar at all 186 187 // Don't know why Ctrl-Alt-Del isn't even listed on official Microsoft support page 188 Shortcut.registerSystemShortcut("system:reset", tr("reserved"), VK_DELETE, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic(); 189 190 // Ease of Access keyboard shortcuts 191 Shortcut.registerSystemShortcut("microsoft-reserved-01", tr("reserved"), VK_PRINTSCREEN, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn High Contrast on or off 192 Shortcut.registerSystemShortcut("microsoft-reserved-02", tr("reserved"), VK_NUM_LOCK, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn Mouse Keys on or off 193 //Shortcut.registerSystemCut("microsoft-reserved-03", tr("reserved"), VK_U, );// Open the Ease of Access Center (TODO: Windows-U, how to handle it in Java ?) 194 195 // General keyboard shortcuts 196 //Shortcut.registerSystemShortcut("system:help", tr("reserved"), VK_F1, 0); // Display Help 197 Shortcut.registerSystemShortcut("system:copy", tr("reserved"), VK_C, CTRL_DOWN_MASK); // Copy the selected item 198 Shortcut.registerSystemShortcut("system:cut", tr("reserved"), VK_X, CTRL_DOWN_MASK); // Cut the selected item 199 Shortcut.registerSystemShortcut("system:paste", tr("reserved"), VK_V, CTRL_DOWN_MASK); // Paste the selected item 200 Shortcut.registerSystemShortcut("system:undo", tr("reserved"), VK_Z, CTRL_DOWN_MASK); // Undo an action 201 Shortcut.registerSystemShortcut("system:redo", tr("reserved"), VK_Y, CTRL_DOWN_MASK); // Redo an action 202 //Shortcut.registerSystemCut("microsoft-reserved-10", tr("reserved"), VK_DELETE, 0); // Delete the selected item and move it to the Recycle Bin 203 //Shortcut.registerSystemCut("microsoft-reserved-11", tr("reserved"), VK_DELETE, SHIFT_DOWN_MASK); // Delete the selected item without moving it to the Recycle Bin first 204 //Shortcut.registerSystemCut("system:rename", tr("reserved"), VK_F2, 0); // Rename the selected item 205 Shortcut.registerSystemShortcut("system:movefocusright", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next word 206 Shortcut.registerSystemShortcut("system:movefocusleft", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous word 207 Shortcut.registerSystemShortcut("system:movefocusdown", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next paragraph 208 Shortcut.registerSystemShortcut("system:movefocusup", tr("reserved"), VK_UP, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous paragraph 209 //Shortcut.registerSystemCut("microsoft-reserved-17", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text 210 //Shortcut.registerSystemCut("microsoft-reserved-18", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text 211 //Shortcut.registerSystemCut("microsoft-reserved-19", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text 212 //Shortcut.registerSystemCut("microsoft-reserved-20", tr("reserved"), VK_UP, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text 213 //Shortcut.registerSystemCut("microsoft-reserved-21", tr("reserved"), VK_RIGHT, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document 214 //Shortcut.registerSystemCut("microsoft-reserved-22", tr("reserved"), VK_LEFT, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document 215 //Shortcut.registerSystemCut("microsoft-reserved-23", tr("reserved"), VK_DOWN, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document 216 //Shortcut.registerSystemCut("microsoft-reserved-24", tr("reserved"), VK_UP, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document 217 //Shortcut.registerSystemCut("microsoft-reserved-25", tr("reserved"), VK_RIGHT+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) 218 //Shortcut.registerSystemCut("microsoft-reserved-26", tr("reserved"), VK_LEFT+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) 219 //Shortcut.registerSystemCut("microsoft-reserved-27", tr("reserved"), VK_DOWN+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) 220 //Shortcut.registerSystemCut("microsoft-reserved-28", tr("reserved"), VK_UP+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) 221 Shortcut.registerSystemShortcut("system:selectall", tr("reserved"), VK_A, CTRL_DOWN_MASK); // Select all items in a document or window 222 //Shortcut.registerSystemCut("system:search", tr("reserved"), VK_F3, 0); // Search for a file or folder 223 Shortcut.registerSystemShortcut("microsoft-reserved-31", tr("reserved"), VK_ENTER, ALT_DOWN_MASK).setAutomatic(); // Display properties for the selected item 224 Shortcut.registerSystemShortcut("system:exit", tr("reserved"), VK_F4, ALT_DOWN_MASK).setAutomatic(); // Close the active item, or exit the active program 225 Shortcut.registerSystemShortcut("microsoft-reserved-33", tr("reserved"), VK_SPACE, ALT_DOWN_MASK).setAutomatic(); // Open the shortcut menu for the active window 226 //Shortcut.registerSystemCut("microsoft-reserved-34", tr("reserved"), VK_F4, CTRL_DOWN_MASK); // Close the active document (in programs that allow you to have multiple documents open simultaneously) 227 Shortcut.registerSystemShortcut("microsoft-reserved-35", tr("reserved"), VK_TAB, ALT_DOWN_MASK).setAutomatic(); // Switch between open items 228 Shortcut.registerSystemShortcut("microsoft-reserved-36", tr("reserved"), VK_TAB, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic(); // Use the arrow keys to switch between open items 229 //Shortcut.registerSystemCut("microsoft-reserved-37", tr("reserved"), VK_TAB, ); // Cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Windows-Tab, how to handle it in Java ?) 230 //Shortcut.registerSystemCut("microsoft-reserved-38", tr("reserved"), VK_TAB, CTRL_DOWN_MASK | ); // Use the arrow keys to cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Ctrl-Windows-Tab, how to handle it in Java ?) 231 Shortcut.registerSystemShortcut("microsoft-reserved-39", tr("reserved"), VK_ESCAPE, ALT_DOWN_MASK).setAutomatic(); // Cycle through items in the order in which they were opened 232 //Shortcut.registerSystemCut("microsoft-reserved-40", tr("reserved"), VK_F6, 0); // Cycle through screen elements in a window or on the desktop 233 //Shortcut.registerSystemCut("microsoft-reserved-41", tr("reserved"), VK_F4, 0); // Display the address bar list in Windows Explorer 234 Shortcut.registerSystemShortcut("microsoft-reserved-42", tr("reserved"), VK_F10, SHIFT_DOWN_MASK); // Display the shortcut menu for the selected item 235 Shortcut.registerSystemShortcut("microsoft-reserved-43", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK).setAutomatic(); // Open the Start menu 236 //Shortcut.registerSystemShortcut("microsoft-reserved-44", tr("reserved"), VK_F10, 0); // Activate the menu bar in the active program 237 //Shortcut.registerSystemCut("microsoft-reserved-45", tr("reserved"), VK_RIGHT, 0); // Open the next menu to the right, or open a submenu 238 //Shortcut.registerSystemCut("microsoft-reserved-46", tr("reserved"), VK_LEFT, 0); // Open the next menu to the left, or close a submenu 239 //Shortcut.registerSystemCut("microsoft-reserved-47", tr("reserved"), VK_F5, 0); // Refresh the active window 240 //Shortcut.registerSystemCut("microsoft-reserved-48", tr("reserved"), VK_UP, ALT_DOWN_MASK); // View the folder one level up in Windows Explorer 241 //Shortcut.registerSystemCut("microsoft-reserved-49", tr("reserved"), VK_ESCAPE, 0); // Cancel the current task 242 Shortcut.registerSystemShortcut("microsoft-reserved-50", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Open Task Manager 243 Shortcut.registerSystemShortcut("microsoft-reserved-51", tr("reserved"), VK_SHIFT, ALT_DOWN_MASK).setAutomatic(); // Switch the input language when multiple input languages are enabled 244 Shortcut.registerSystemShortcut("microsoft-reserved-52", tr("reserved"), VK_SHIFT, CTRL_DOWN_MASK).setAutomatic(); // Switch the keyboard layout when multiple keyboard layouts are enabled 245 //Shortcut.registerSystemCut("microsoft-reserved-53", tr("reserved"), ); // Change the reading direction of text in right-to-left reading languages (TODO: unclear) 246 // CHECKSTYLE.ON: LineLength 247 } 248 249 @Override 250 public String getDefaultStyle() { 251 return "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; 252 } 253 254 @Override 255 public boolean rename(File from, File to) { 256 if (to.exists()) 257 Utils.deleteFile(to); 258 return from.renameTo(to); 259 } 260 261 @Override 262 public String getOSDescription() { 263 return Utils.strip(getSystemProperty("os.name")) + ' ' + 264 ((getSystemEnv("ProgramFiles(x86)") == null) ? "32" : "64") + "-Bit"; 265 } 266 267 /** 268 * Returns the Windows product name from registry (example: "Windows 10 Pro") 269 * @return the Windows product name from registry 270 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible 271 * @throws InvocationTargetException if the underlying method throws an exception 272 * @since 12744 273 */ 274 public static String getProductName() throws IllegalAccessException, InvocationTargetException { 275 return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "ProductName"); 276 } 277 278 /** 279 * Returns the Windows release identifier from registry (example: "1703") 280 * @return the Windows release identifier from registry 281 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible 282 * @throws InvocationTargetException if the underlying method throws an exception 283 * @since 12744 284 */ 285 public static String getReleaseId() throws IllegalAccessException, InvocationTargetException { 286 return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "ReleaseId"); 287 } 288 289 /** 290 * Returns the Windows current build number from registry (example: "15063") 291 * @return the Windows current build number from registry 292 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible 293 * @throws InvocationTargetException if the underlying method throws an exception 294 * @since 12744 295 */ 296 public static String getCurrentBuild() throws IllegalAccessException, InvocationTargetException { 297 return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "CurrentBuild"); 298 } 299 300 private static String buildOSBuildNumber() { 301 StringBuilder sb = new StringBuilder(); 302 try { 303 sb.append(getProductName()); 304 String releaseId = getReleaseId(); 305 if (releaseId != null) { 306 sb.append(' ').append(releaseId); 307 } 308 sb.append(" (").append(getCurrentBuild()).append(')'); 309 } catch (ReflectiveOperationException | JosmRuntimeException | NoClassDefFoundError e) { 310 Logging.log(Logging.LEVEL_ERROR, "Unable to get Windows build number", e); 311 Logging.debug(e); 312 } 313 return sb.toString(); 314 } 315 316 @Override 317 public String getOSBuildNumber() { 318 if (oSBuildNumber == null) { 319 oSBuildNumber = buildOSBuildNumber(); 320 } 321 return oSBuildNumber; 322 } 323 324 /** 325 * Loads Windows-ROOT keystore. 326 * @return Windows-ROOT keystore 327 * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found 328 * @throws CertificateException if any of the certificates in the keystore could not be loaded 329 * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given 330 * @throws KeyStoreException if no Provider supports a KeyStore implementation for the type "Windows-ROOT" 331 * @since 7343 332 */ 333 public static KeyStore getRootKeystore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException { 334 KeyStore ks = KeyStore.getInstance(WINDOWS_ROOT); 335 ks.load(null, null); 336 return ks; 337 } 338 339 @Override 340 public X509Certificate getX509Certificate(NativeCertAmend certAmend) 341 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { 342 // Get Windows Trust Root Store 343 KeyStore ks = getRootKeystore(); 344 // Search by alias (fast) 345 for (String winAlias : certAmend.getNativeAliases()) { 346 Certificate result = ks.getCertificate(winAlias); 347 if (result == null && !NetworkManager.isOffline(OnlineResource.CERTIFICATES)) { 348 // Make a web request to target site to force Windows to update if needed its trust root store from its certificate trust list 349 // A better, but a lot more complex method might be to get certificate list from Windows Registry with PowerShell 350 // using (Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\SystemCertificates\\AuthRoot\\AutoUpdate').EncodedCtl) 351 // then decode it using CertUtil -dump or calling CertCreateCTLContext API using JNI, and finally find and decode the certificate 352 Logging.trace(webRequest(certAmend.getWebSite())); 353 // Reload Windows Trust Root Store and search again by alias (fast) 354 ks = getRootKeystore(); 355 result = ks.getCertificate(winAlias); 356 } 357 if (result instanceof X509Certificate) { 358 return (X509Certificate) result; 359 } 360 } 361 // If not found, search by SHA-256 (slower) 362 MessageDigest md = MessageDigest.getInstance("SHA-256"); 363 for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements();) { 364 String alias = aliases.nextElement(); 365 Certificate result = ks.getCertificate(alias); 366 if (result instanceof X509Certificate 367 && certAmend.getSha256().equalsIgnoreCase(Utils.toHexString(md.digest(result.getEncoded())))) { 368 Logging.warn("Certificate not found for alias ''{0}'' but found for alias ''{1}''", certAmend.getNativeAliases(), alias); 369 return (X509Certificate) result; 370 } 371 } 372 // Not found 373 return null; 374 } 375 376 @Override 377 public File getDefaultCacheDirectory() { 378 String p = getSystemEnv("LOCALAPPDATA"); 379 if (p == null || p.isEmpty()) { 380 // Fallback for Windows OS earlier than Windows Vista, where the variable is not defined 381 p = getSystemEnv("APPDATA"); 382 } 383 return new File(new File(p, Preferences.getJOSMDirectoryBaseName()), "cache"); 384 } 385 386 @Override 387 public File getDefaultPrefDirectory() { 388 return new File(getSystemEnv("APPDATA"), Preferences.getJOSMDirectoryBaseName()); 389 } 390 391 @Override 392 public File getDefaultUserDataDirectory() { 393 // Use preferences directory by default 394 return Config.getDirs().getPreferencesDirectory(false); 395 } 396 397 /** 398 * <p>Add more fallback fonts to the Java runtime, in order to get 399 * support for more scripts.</p> 400 * 401 * <p>The font configuration in Java doesn't include some Indic scripts, 402 * even though MS Windows ships with fonts that cover these unicode ranges.</p> 403 * 404 * <p>To fix this, the fontconfig.properties template is copied to the JOSM 405 * cache folder. Then, the additional entries are added to the font 406 * configuration. Finally the system property "sun.awt.fontconfig" is set 407 * to the customized fontconfig.properties file.</p> 408 * 409 * <p>This is a crude hack, but better than no font display at all for these languages. 410 * There is no guarantee, that the template file 411 * ($JAVA_HOME/lib/fontconfig.properties.src) matches the default 412 * configuration (which is in a binary format). 413 * Furthermore, the system property "sun.awt.fontconfig" is undocumented and 414 * may no longer work in future versions of Java.</p> 415 * 416 * <p>Related Java bug: <a href="https://bugs.openjdk.java.net/browse/JDK-8008572">JDK-8008572</a></p> 417 * 418 * @param templateFileName file name of the fontconfig.properties template file 419 */ 420 protected void extendFontconfig(String templateFileName) { 421 String customFontconfigFile = Config.getPref().get("fontconfig.properties", null); 422 if (customFontconfigFile != null) { 423 Utils.updateSystemProperty("sun.awt.fontconfig", customFontconfigFile); 424 return; 425 } 426 if (!Config.getPref().getBoolean("font.extended-unicode", true)) 427 return; 428 429 String javaLibPath = getSystemProperty("java.home") + File.separator + "lib"; 430 Path templateFile = FileSystems.getDefault().getPath(javaLibPath, templateFileName); 431 String templatePath = templateFile.toString(); 432 if (templatePath.startsWith("null") || !Files.isReadable(templateFile)) { 433 Logging.warn("extended font config - unable to find font config template file {0}", templatePath); 434 return; 435 } 436 try (InputStream fis = Files.newInputStream(templateFile)) { 437 Properties props = new Properties(); 438 props.load(fis); 439 byte[] content = Files.readAllBytes(templateFile); 440 File cachePath = Config.getDirs().getCacheDirectory(true); 441 Path fontconfigFile = cachePath.toPath().resolve("fontconfig.properties"); 442 OutputStream os = Files.newOutputStream(fontconfigFile); 443 os.write(content); 444 try (Writer w = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) { 445 Collection<FontEntry> extrasPref = StructUtils.getListOfStructs(Config.getPref(), 446 "font.extended-unicode.extra-items", getAdditionalFonts(), FontEntry.class); 447 Collection<FontEntry> extras = new ArrayList<>(); 448 w.append("\n\n# Added by JOSM to extend unicode coverage of Java font support:\n\n"); 449 List<String> allCharSubsets = new ArrayList<>(); 450 for (FontEntry entry: extrasPref) { 451 Collection<String> fontsAvail = getInstalledFonts(); 452 if (fontsAvail != null && fontsAvail.contains(entry.file.toUpperCase(Locale.ENGLISH))) { 453 if (!allCharSubsets.contains(entry.charset)) { 454 allCharSubsets.add(entry.charset); 455 extras.add(entry); 456 } else { 457 Logging.trace("extended font config - already registered font for charset ''{0}'' - skipping ''{1}''", 458 entry.charset, entry.name); 459 } 460 } else { 461 Logging.trace("extended font config - Font ''{0}'' not found on system - skipping", entry.name); 462 } 463 } 464 for (FontEntry entry: extras) { 465 allCharSubsets.add(entry.charset); 466 if ("".equals(entry.name)) { 467 continue; 468 } 469 String key = "allfonts." + entry.charset; 470 String value = entry.name; 471 String prevValue = props.getProperty(key); 472 if (prevValue != null && !prevValue.equals(value)) { 473 Logging.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value); 474 } 475 w.append(key + '=' + value + '\n'); 476 } 477 w.append('\n'); 478 for (FontEntry entry: extras) { 479 if ("".equals(entry.name) || "".equals(entry.file)) { 480 continue; 481 } 482 String key = "filename." + entry.name.replace(' ', '_'); 483 String value = entry.file; 484 String prevValue = props.getProperty(key); 485 if (prevValue != null && !prevValue.equals(value)) { 486 Logging.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value); 487 } 488 w.append(key + '=' + value + '\n'); 489 } 490 w.append('\n'); 491 String fallback = props.getProperty("sequence.fallback"); 492 if (fallback != null) { 493 w.append("sequence.fallback=" + fallback + ',' + Utils.join(",", allCharSubsets) + '\n'); 494 } else { 495 w.append("sequence.fallback=" + Utils.join(",", allCharSubsets) + '\n'); 496 } 497 } 498 Utils.updateSystemProperty("sun.awt.fontconfig", fontconfigFile.toString()); 499 } catch (IOException | InvalidPathException ex) { 500 Logging.error(ex); 501 } 502 } 503 504 /** 505 * Get a list of fonts that are installed on the system. 506 * 507 * Must be done without triggering the Java Font initialization. 508 * (See {@link #extendFontconfig(java.lang.String)}, have to set system 509 * property first, which is then read by sun.awt.FontConfiguration upon initialization.) 510 * 511 * @return list of file names 512 */ 513 protected Collection<String> getInstalledFonts() { 514 // Cannot use GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames() 515 // because we have to set the system property before Java initializes its fonts. 516 // Use more low-level method to find the installed fonts. 517 List<String> fontsAvail = new ArrayList<>(); 518 Path fontPath = FileSystems.getDefault().getPath(getSystemEnv("SYSTEMROOT"), "Fonts"); 519 try (DirectoryStream<Path> ds = Files.newDirectoryStream(fontPath)) { 520 for (Path p : ds) { 521 Path filename = p.getFileName(); 522 if (filename != null) { 523 fontsAvail.add(filename.toString().toUpperCase(Locale.ENGLISH)); 524 } 525 } 526 fontsAvail.add(""); // for devanagari 527 } catch (IOException | DirectoryIteratorException ex) { 528 Logging.log(Logging.LEVEL_ERROR, ex); 529 Logging.warn("extended font config - failed to load available Fonts"); 530 fontsAvail = null; 531 } 532 return fontsAvail; 533 } 534 535 /** 536 * Get default list of additional fonts to add to the configuration. 537 * 538 * Java will choose thee first font in the list that can render a certain character. 539 * 540 * @return list of FontEntry objects 541 */ 542 protected Collection<FontEntry> getAdditionalFonts() { 543 Collection<FontEntry> def = new ArrayList<>(33); 544 def.add(new FontEntry("devanagari", "", "")); // just include in fallback list font already defined in template 545 546 // Windows scripts: https://msdn.microsoft.com/en-us/goglobal/bb688099.aspx 547 // IE default fonts: https://msdn.microsoft.com/en-us/library/ie/dn467844(v=vs.85).aspx 548 549 // Windows 10 and later 550 def.add(new FontEntry("historic", "Segoe UI Historic", "SEGUIHIS.TTF")); // historic charsets 551 552 // Windows 8/8.1 and later 553 def.add(new FontEntry("javanese", "Javanese Text", "JAVATEXT.TTF")); // ISO 639: jv 554 def.add(new FontEntry("leelawadee", "Leelawadee", "LEELAWAD.TTF")); // ISO 639: bug 555 def.add(new FontEntry("malgun", "Malgun Gothic", "MALGUN.TTF")); // ISO 639: ko 556 def.add(new FontEntry("myanmar", "Myanmar Text", "MMRTEXT.TTF")); // ISO 639: my 557 def.add(new FontEntry("nirmala", "Nirmala UI", "NIRMALA.TTF")); // ISO 639: sat,srb 558 def.add(new FontEntry("segoeui", "Segoe UI", "SEGOEUI.TTF")); // ISO 639: lis 559 def.add(new FontEntry("emoji", "Segoe UI Emoji", "SEGUIEMJ.TTF")); // emoji symbol characters 560 561 // Windows 7 and later 562 def.add(new FontEntry("nko_tifinagh_vai_osmanya", "Ebrima", "EBRIMA.TTF")); // ISO 639: ber. Nko only since Win 8 563 def.add(new FontEntry("khmer1", "Khmer UI", "KHMERUI.TTF")); // ISO 639: km 564 def.add(new FontEntry("lao1", "Lao UI", "LAOUI.TTF")); // ISO 639: lo 565 def.add(new FontEntry("tai_le", "Microsoft Tai Le", "TAILE.TTF")); // ISO 639: khb 566 def.add(new FontEntry("new_tai_lue", "Microsoft New Tai Lue", "NTHAILU.TTF")); // ISO 639: khb 567 568 // Windows Vista and later: 569 def.add(new FontEntry("ethiopic", "Nyala", "NYALA.TTF")); // ISO 639: am,gez,ti 570 def.add(new FontEntry("tibetan", "Microsoft Himalaya", "HIMALAYA.TTF")); // ISO 639: bo,dz 571 def.add(new FontEntry("cherokee", "Plantagenet Cherokee", "PLANTC.TTF")); // ISO 639: chr 572 def.add(new FontEntry("unified_canadian", "Euphemia", "EUPHEMIA.TTF")); // ISO 639: cr,in 573 def.add(new FontEntry("khmer2", "DaunPenh", "DAUNPENH.TTF")); // ISO 639: km 574 def.add(new FontEntry("khmer3", "MoolBoran", "MOOLBOR.TTF")); // ISO 639: km 575 def.add(new FontEntry("lao_thai", "DokChampa", "DOKCHAMP.TTF")); // ISO 639: lo 576 def.add(new FontEntry("mongolian", "Mongolian Baiti", "MONBAITI.TTF")); // ISO 639: mn 577 def.add(new FontEntry("oriya", "Kalinga", "KALINGA.TTF")); // ISO 639: or 578 def.add(new FontEntry("sinhala", "Iskoola Pota", "ISKPOTA.TTF")); // ISO 639: si 579 def.add(new FontEntry("yi", "Yi Baiti", "MSYI.TTF")); // ISO 639: ii 580 581 // Windows XP and later 582 def.add(new FontEntry("gujarati", "Shruti", "SHRUTI.TTF")); 583 def.add(new FontEntry("kannada", "Tunga", "TUNGA.TTF")); 584 def.add(new FontEntry("gurmukhi", "Raavi", "RAAVI.TTF")); 585 def.add(new FontEntry("telugu", "Gautami", "GAUTAMI.TTF")); 586 def.add(new FontEntry("bengali", "Vrinda", "VRINDA.TTF")); // since XP SP2 587 def.add(new FontEntry("syriac", "Estrangelo Edessa", "ESTRE.TTF")); // ISO 639: arc 588 def.add(new FontEntry("thaana", "MV Boli", "MVBOLI.TTF")); // ISO 639: dv 589 def.add(new FontEntry("malayalam", "Kartika", "KARTIKA.TTF")); // ISO 639: ml; since XP SP2 590 591 // Windows 2000 and later 592 def.add(new FontEntry("tamil", "Latha", "LATHA.TTF")); 593 594 // Comes with MS Office & Outlook 2000. Good unicode coverage, so add if available. 595 def.add(new FontEntry("arialuni", "Arial Unicode MS", "ARIALUNI.TTF")); 596 597 return def; 598 } 599 600 /** 601 * Determines if the .NET framework 4.5 (or later) is installed. 602 * Windows 7 ships by default with an older version. 603 * @return {@code true} if the .NET framework 4.5 (or later) is installed. 604 * @since 13463 605 */ 606 public static boolean isDotNet45Installed() { 607 try { 608 // https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#net_d 609 // "The existence of the Release DWORD indicates that the .NET Framework 4.5 or later has been installed" 610 // Great, but our WinRegistry only handles REG_SZ type, so we have to check the Version key 611 String version = WinRegistry.readString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full", "Version"); 612 if (version != null) { 613 Matcher m = MS_VERSION_PATTERN.matcher(version); 614 if (m.matches()) { 615 int maj = Integer.parseInt(m.group(1)); 616 int min = Integer.parseInt(m.group(2)); 617 return (maj == 4 && min >= 5) || maj > 4; 618 } 619 } 620 } catch (IllegalAccessException | InvocationTargetException | NumberFormatException e) { 621 Logging.error(e); 622 } 623 return false; 624 } 625 626 /** 627 * Returns the major version number of PowerShell. 628 * @return the major version number of PowerShell. -1 in case of error 629 * @since 13465 630 */ 631 public static int getPowerShellVersion() { 632 try { 633 String version = WinRegistry.readString( 634 HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Powershell\\3\\PowershellEngine", "PowershellVersion"); 635 if (version != null) { 636 Matcher m = MS_VERSION_PATTERN.matcher(version); 637 if (m.matches()) { 638 return Integer.parseInt(m.group(1)); 639 } 640 } 641 } catch (NumberFormatException | IllegalAccessException | InvocationTargetException e) { 642 Logging.error(e); 643 } 644 return -1; 645 } 646 647 /** 648 * Performs a web request using Windows CryptoAPI (through PowerShell). 649 * This is useful to ensure Windows trust store will contain a specific root CA. 650 * @param uri the web URI to request 651 * @return HTTP response from the given URI 652 * @throws IOException if any I/O error occurs 653 * @since 13458 654 */ 655 public static String webRequest(String uri) throws IOException { 656 // With PS 6.0 (not yet released in Windows) we could simply use: 657 // Invoke-WebRequest -SSlProtocol Tsl12 $uri 658 // .NET framework < 4.5 does not support TLS 1.2 (https://stackoverflow.com/a/43240673/2257172) 659 if (isDotNet45Installed() && getPowerShellVersion() >= 3) { 660 try { 661 // The following works with PS 3.0 (Windows 8+), https://stackoverflow.com/a/41618979/2257172 662 return Utils.execOutput(Arrays.asList("powershell", "-Command", 663 "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;"+ 664 "[System.Net.WebRequest]::Create('"+uri+"').GetResponse()" 665 ), 5, TimeUnit.SECONDS); 666 } catch (ExecutionException | InterruptedException e) { 667 Logging.warn("Unable to request certificate of " + uri); 668 Logging.debug(e); 669 } 670 } 671 return null; 672 } 673 674 @Override 675 public File resolveFileLink(File file) { 676 if (file.getName().endsWith(".lnk")) { 677 try { 678 return new File(new WindowsShortcut(file).getRealFilename()); 679 } catch (IOException | ParseException e) { 680 Logging.error(e); 681 } 682 } 683 return file; 684 } 685 686 @Override 687 public Collection<String> getPossiblePreferenceDirs() { 688 Set<String> locations = new HashSet<>(); 689 String appdata = getSystemEnv("APPDATA"); 690 if (appdata != null && getSystemEnv("ALLUSERSPROFILE") != null 691 && appdata.lastIndexOf(File.separator) != -1) { 692 appdata = appdata.substring(appdata.lastIndexOf(File.separator)); 693 locations.add(new File(new File(getSystemEnv("ALLUSERSPROFILE"), appdata), "JOSM").getPath()); 694 } 695 return locations; 696 } 697}