source: josm/trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java@ 17333

Last change on this file since 17333 was 16924, checked in by simon04, 4 years ago

Java Warnings

  • Property svn:eol-style set to native
File size: 18.9 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[1023]2package org.openstreetmap.josm.tools;
3
[4998]4import static org.openstreetmap.josm.tools.I18n.tr;
[13647]5import static org.openstreetmap.josm.tools.Utils.getSystemEnv;
6import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
[4998]7
[6682]8import java.awt.Desktop;
[1023]9import java.awt.event.KeyEvent;
[5850]10import java.io.BufferedReader;
[4153]11import java.io.File;
[1023]12import java.io.IOException;
[13204]13import java.io.InputStream;
[6682]14import java.net.URISyntaxException;
[7082]15import java.nio.charset.StandardCharsets;
[7314]16import java.nio.file.Files;
[7315]17import java.nio.file.Path;
[7314]18import java.nio.file.Paths;
[12241]19import java.security.KeyStoreException;
20import java.security.NoSuchAlgorithmException;
21import java.security.cert.CertificateException;
22import java.security.cert.CertificateFactory;
23import java.security.cert.X509Certificate;
[6103]24import java.util.Arrays;
[14144]25import java.util.Collection;
26import java.util.HashSet;
[11162]27import java.util.Locale;
[16911]28import java.util.Optional;
[14144]29import java.util.Set;
[12830]30import java.util.concurrent.ExecutionException;
[1023]31
[14148]32import org.openstreetmap.josm.data.Preferences;
[13450]33import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend;
[12846]34import org.openstreetmap.josm.spi.preferences.Config;
[6310]35
[1023]36/**
[12219]37 * {@code PlatformHook} implementation for Unix systems.
38 * @since 1023
[2376]39 */
[1023]40public class PlatformHookUnixoid implements PlatformHook {
[6070]41
[5994]42 private String osDescription;
[6070]43
[4897]44 @Override
[12776]45 public Platform getPlatform() {
46 return Platform.UNIXOID;
47 }
48
49 @Override
[6443]50 public void preStartupHook() {
[14177]51 // See #12022, #16666 - Disable GNOME ATK Java wrapper as it causes a lot of serious trouble
52 if (isDebianOrUbuntu()) {
53 if (Utils.getJavaVersion() >= 9) {
54 // TODO: find a way to disable ATK wrapper on Java >= 9
55 // We should probably be able to do that by embedding a no-op AccessibilityProvider in our jar
56 // so that it is loaded by ServiceLoader without error
57 // But this require to compile at least one class with Java 9
58 } else {
59 // Java 8 does a simple Class.newInstance() from system classloader
60 Utils.updateSystemProperty("javax.accessibility.assistive_technologies", "java.lang.Object");
61 }
[9750]62 }
[1169]63 }
[4897]64
65 @Override
[1169]66 public void openUrl(String url) throws IOException {
[12846]67 for (String program : Config.getPref().getList("browser.unix",
[6682]68 Arrays.asList("xdg-open", "#DESKTOP#", "$BROWSER", "gnome-open", "kfmclient openURL", "firefox"))) {
[1169]69 try {
[6682]70 if ("#DESKTOP#".equals(program)) {
[15543]71 Desktop.getDesktop().browse(Utils.urlToURI(url));
[6682]72 } else if (program.startsWith("$")) {
73 program = System.getenv().get(program.substring(1));
74 Runtime.getRuntime().exec(new String[]{program, url});
75 } else {
76 Runtime.getRuntime().exec(new String[]{program, url});
77 }
[1169]78 return;
[7004]79 } catch (IOException | URISyntaxException e) {
[12620]80 Logging.warn(e);
[1169]81 }
82 }
83 }
[1023]84
[4897]85 @Override
[1169]86 public void initSystemShortcuts() {
[8518]87 // CHECKSTYLE.OFF: LineLength
[4897]88 // TODO: Insert system shortcuts here. See Windows and especially OSX to see how to.
[8513]89 for (int i = KeyEvent.VK_F1; i <= KeyEvent.VK_F12; ++i) {
[8518]90 Shortcut.registerSystemShortcut("screen:toogle"+i, tr("reserved"), i, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
91 .setAutomatic();
[8513]92 }
[8518]93 Shortcut.registerSystemShortcut("system:reset", tr("reserved"), KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
94 .setAutomatic();
95 Shortcut.registerSystemShortcut("system:resetX", tr("reserved"), KeyEvent.VK_BACK_SPACE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
96 .setAutomatic();
97 // CHECKSTYLE.ON: LineLength
[1169]98 }
[6920]99
[4897]100 @Override
[2376]101 public String getDefaultStyle() {
[2371]102 return "javax.swing.plaf.metal.MetalLookAndFeel";
103 }
[4153]104
[6103]105 /**
[16911]106 * Returns desktop environment based on the environment variable {@code XDG_CURRENT_DESKTOP}.
107 * @return desktop environment.
108 */
109 public Optional<String> getDesktopEnvironment() {
[16924]110 return Optional.ofNullable(getSystemEnv("XDG_CURRENT_DESKTOP")).filter(s -> !s.isEmpty());
[16911]111 }
112
113 /**
[10166]114 * Determines if the distribution is Debian or Ubuntu, or a derivative.
115 * @return {@code true} if the distribution is Debian, Ubuntu or Mint, {@code false} otherwise
116 */
117 public static boolean isDebianOrUbuntu() {
118 try {
119 String dist = Utils.execOutput(Arrays.asList("lsb_release", "-i", "-s"));
120 return "Debian".equalsIgnoreCase(dist) || "Ubuntu".equalsIgnoreCase(dist) || "Mint".equalsIgnoreCase(dist);
[12830]121 } catch (IOException | ExecutionException | InterruptedException e) {
[10627]122 // lsb_release is not available on all Linux systems, so don't log at warning level
[12620]123 Logging.debug(e);
[10166]124 return false;
125 }
126 }
127
128 /**
[6850]129 * Get the package name including detailed version.
[7314]130 * @param packageNames The possible package names (when a package can have different names on different distributions)
[6850]131 * @return The package name and package version if it can be identified, null otherwise
[7314]132 * @since 7314
[6850]133 */
[11747]134 public static String getPackageDetails(String... packageNames) {
[6850]135 try {
[10378]136 // CHECKSTYLE.OFF: SingleSpaceSeparator
[11096]137 boolean dpkg = Paths.get("/usr/bin/dpkg-query").toFile().exists();
138 boolean eque = Paths.get("/usr/bin/equery").toFile().exists();
139 boolean rpm = Paths.get("/bin/rpm").toFile().exists();
[10378]140 // CHECKSTYLE.ON: SingleSpaceSeparator
[7349]141 if (dpkg || rpm || eque) {
[7314]142 for (String packageName : packageNames) {
[10308]143 String[] args;
[7314]144 if (dpkg) {
145 args = new String[] {"dpkg-query", "--show", "--showformat", "${Architecture}-${Version}", packageName};
[7349]146 } else if (eque) {
147 args = new String[] {"equery", "-q", "list", "-e", "--format=$fullversion", packageName};
[7314]148 } else {
149 args = new String[] {"rpm", "-q", "--qf", "%{arch}-%{version}", packageName};
150 }
[12830]151 try {
152 String version = Utils.execOutput(Arrays.asList(args));
153 if (version != null && !version.isEmpty()) {
154 return packageName + ':' + version;
[12239]155 }
[12830]156 } catch (ExecutionException e) {
157 // Package does not exist, continue
158 Logging.trace(e);
[7314]159 }
160 }
[6962]161 }
[12830]162 } catch (IOException | InterruptedException e) {
[12620]163 Logging.warn(e);
[6850]164 }
[6962]165 return null;
[6850]166 }
[6920]167
[6850]168 /**
[6103]169 * Get the Java package name including detailed version.
170 *
171 * Some Java bugs are specific to a certain security update, so in addition
172 * to the Java version, we also need the exact package version.
173 *
[7314]174 * @return The package name and package version if it can be identified, null otherwise
[6103]175 */
176 public String getJavaPackageDetails() {
[13647]177 String home = getSystemProperty("java.home");
[10580]178 if (home.contains("java-8-openjdk") || home.contains("java-1.8.0-openjdk")) {
179 return getPackageDetails("openjdk-8-jre", "java-1_8_0-openjdk", "java-1.8.0-openjdk");
180 } else if (home.contains("java-9-openjdk") || home.contains("java-1.9.0-openjdk")) {
[12830]181 return getPackageDetails("openjdk-9-jre", "java-1_9_0-openjdk", "java-1.9.0-openjdk", "java-9-openjdk");
[13695]182 } else if (home.contains("java-10-openjdk")) {
183 return getPackageDetails("openjdk-10-jre", "java-10-openjdk");
184 } else if (home.contains("java-11-openjdk")) {
185 return getPackageDetails("openjdk-11-jre", "java-11-openjdk");
186 } else if (home.contains("java-openjdk")) {
187 return getPackageDetails("java-openjdk");
[7349]188 } else if (home.contains("icedtea")) {
189 return getPackageDetails("icedtea-bin");
190 } else if (home.contains("oracle")) {
191 return getPackageDetails("oracle-jdk-bin", "oracle-jre-bin");
[6103]192 }
193 return null;
194 }
[6920]195
[6850]196 /**
197 * Get the Web Start package name including detailed version.
198 *
[7314]199 * OpenJDK packages are shipped with icedtea-web package,
200 * but its version generally does not match main java package version.
[6920]201 *
[6850]202 * Simply return {@code null} if there's no separate package for Java WebStart.
203 *
204 * @return The package name and package version if it can be identified, null otherwise
205 */
206 public String getWebStartPackageDetails() {
[7314]207 if (isOpenJDK()) {
208 return getPackageDetails("icedtea-netx", "icedtea-web");
[6850]209 }
210 return null;
211 }
[6103]212
[10734]213 /**
214 * Get the Gnome ATK wrapper package name including detailed version.
215 *
216 * Debian and Ubuntu derivatives come with a pre-enabled accessibility software
217 * completely buggy that makes Swing crash in a lot of different ways.
218 *
219 * Simply return {@code null} if it's not found.
220 *
221 * @return The package name and package version if it can be identified, null otherwise
222 */
223 public String getAtkWrapperPackageDetails() {
224 if (isOpenJDK() && isDebianOrUbuntu()) {
225 return getPackageDetails("libatk-wrapper-java");
226 }
227 return null;
228 }
229
[12219]230 private String buildOSDescription() {
[13647]231 String osName = getSystemProperty("os.name");
[5850]232 if ("Linux".equalsIgnoreCase(osName)) {
233 try {
[8540]234 // Try lsb_release (only available on LSB-compliant Linux systems,
235 // see https://www.linuxbase.org/lsb-cert/productdir.php?by_prod )
[12411]236 String line = exec("lsb_release", "-ds");
[12217]237 if (line != null && !line.isEmpty()) {
238 line = line.replaceAll("\"+", "");
[15293]239 line = line.replace("NAME=", ""); // strange code for some Gentoo's
[12217]240 if (line.startsWith("Linux ")) // e.g. Linux Mint
241 return line;
242 else if (!line.isEmpty())
243 return "Linux " + line;
[5850]244 }
245 } catch (IOException e) {
[12620]246 Logging.debug(e);
[5877]247 // Non LSB-compliant Linux system. List of common fallback release files: http://linuxmafia.com/faq/Admin/release-files.html
248 for (LinuxReleaseInfo info : new LinuxReleaseInfo[]{
249 new LinuxReleaseInfo("/etc/lsb-release", "DISTRIB_DESCRIPTION", "DISTRIB_ID", "DISTRIB_RELEASE"),
250 new LinuxReleaseInfo("/etc/os-release", "PRETTY_NAME", "NAME", "VERSION"),
251 new LinuxReleaseInfo("/etc/arch-release"),
252 new LinuxReleaseInfo("/etc/debian_version", "Debian GNU/Linux "),
253 new LinuxReleaseInfo("/etc/fedora-release"),
254 new LinuxReleaseInfo("/etc/gentoo-release"),
[7314]255 new LinuxReleaseInfo("/etc/redhat-release"),
256 new LinuxReleaseInfo("/etc/SuSE-release")
[5877]257 }) {
258 String description = info.extractDescription();
259 if (description != null && !description.isEmpty()) {
260 return "Linux " + description;
261 }
262 }
[5850]263 }
264 }
265 return osName;
266 }
[6070]267
[5994]268 @Override
269 public String getOSDescription() {
270 if (osDescription == null) {
271 osDescription = buildOSDescription();
272 }
273 return osDescription;
274 }
[6070]275
[12219]276 private static class LinuxReleaseInfo {
[5877]277 private final String path;
278 private final String descriptionField;
279 private final String idField;
280 private final String releaseField;
281 private final boolean plainText;
282 private final String prefix;
[6070]283
[12219]284 LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField) {
[5877]285 this(path, descriptionField, idField, releaseField, false, null);
286 }
287
[12219]288 LinuxReleaseInfo(String path) {
[5877]289 this(path, null, null, null, true, null);
290 }
291
[12219]292 LinuxReleaseInfo(String path, String prefix) {
[5877]293 this(path, null, null, null, true, prefix);
294 }
[6070]295
[5877]296 private LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField, boolean plainText, String prefix) {
297 this.path = path;
298 this.descriptionField = descriptionField;
299 this.idField = idField;
300 this.releaseField = releaseField;
301 this.plainText = plainText;
302 this.prefix = prefix;
303 }
304
[12219]305 @Override
306 public String toString() {
[6070]307 return "ReleaseInfo [path=" + path + ", descriptionField=" + descriptionField +
[8846]308 ", idField=" + idField + ", releaseField=" + releaseField + ']';
[5877]309 }
[6070]310
[5877]311 /**
312 * Extracts OS detailed information from a Linux release file (/etc/xxx-release)
313 * @return The OS detailed information, or {@code null}
314 */
315 public String extractDescription() {
316 String result = null;
317 if (path != null) {
[7315]318 Path p = Paths.get(path);
[11096]319 if (p.toFile().exists()) {
[7315]320 try (BufferedReader reader = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
[5877]321 String id = null;
322 String release = null;
323 String line;
324 while (result == null && (line = reader.readLine()) != null) {
325 if (line.contains("=")) {
[16643]326 String[] tokens = line.split("=", -1);
[5877]327 if (tokens.length >= 2) {
328 // Description, if available, contains exactly what we need
329 if (descriptionField != null && descriptionField.equalsIgnoreCase(tokens[0])) {
330 result = Utils.strip(tokens[1]);
331 } else if (idField != null && idField.equalsIgnoreCase(tokens[0])) {
332 id = Utils.strip(tokens[1]);
333 } else if (releaseField != null && releaseField.equalsIgnoreCase(tokens[0])) {
334 release = Utils.strip(tokens[1]);
335 }
336 }
337 } else if (plainText && !line.isEmpty()) {
338 // Files composed of a single line
339 result = Utils.strip(line);
340 }
341 }
342 // If no description has been found, try to rebuild it with "id" + "release" (i.e. "name" + "version")
343 if (result == null && id != null && release != null) {
[8846]344 result = id + ' ' + release;
[5877]345 }
346 } catch (IOException e) {
347 // Ignore
[12620]348 Logging.trace(e);
[5877]349 }
350 }
351 }
352 // Append prefix if any
353 if (result != null && !result.isEmpty() && prefix != null && !prefix.isEmpty()) {
354 result = prefix + result;
355 }
[8510]356 if (result != null)
357 result = result.replaceAll("\"+", "");
[5877]358 return result;
359 }
360 }
[6920]361
[11162]362 /**
363 * Get the dot directory <code>~/.josm</code>.
364 * @return the dot directory
365 */
[11218]366 private static File getDotDirectory() {
[14148]367 String dirName = "." + Preferences.getJOSMDirectoryBaseName().toLowerCase(Locale.ENGLISH);
[13647]368 return new File(getSystemProperty("user.home"), dirName);
[11162]369 }
370
371 /**
372 * Returns true if the dot directory should be used for storing preferences,
373 * cache and user data.
374 * Currently this is the case, if the dot directory already exists.
375 * @return true if the dot directory should be used
376 */
[11613]377 private static boolean useDotDirectory() {
[11162]378 return getDotDirectory().exists();
379 }
380
[7206]381 @Override
[7829]382 public File getDefaultCacheDirectory() {
[11162]383 if (useDotDirectory()) {
384 return new File(getDotDirectory(), "cache");
385 } else {
[13647]386 String xdgCacheDir = getSystemEnv("XDG_CACHE_HOME");
[11162]387 if (xdgCacheDir != null && !xdgCacheDir.isEmpty()) {
[14148]388 return new File(xdgCacheDir, Preferences.getJOSMDirectoryBaseName());
[11162]389 } else {
[13647]390 return new File(getSystemProperty("user.home") + File.separator +
[14148]391 ".cache" + File.separator + Preferences.getJOSMDirectoryBaseName());
[11162]392 }
393 }
[7829]394 }
[7831]395
396 @Override
397 public File getDefaultPrefDirectory() {
[11162]398 if (useDotDirectory()) {
399 return getDotDirectory();
400 } else {
[13647]401 String xdgConfigDir = getSystemEnv("XDG_CONFIG_HOME");
[11162]402 if (xdgConfigDir != null && !xdgConfigDir.isEmpty()) {
[14148]403 return new File(xdgConfigDir, Preferences.getJOSMDirectoryBaseName());
[11162]404 } else {
[13647]405 return new File(getSystemProperty("user.home") + File.separator +
[14148]406 ".config" + File.separator + Preferences.getJOSMDirectoryBaseName());
[11162]407 }
408 }
[7831]409 }
[7834]410
411 @Override
412 public File getDefaultUserDataDirectory() {
[11162]413 if (useDotDirectory()) {
414 return getDotDirectory();
415 } else {
[13647]416 String xdgDataDir = getSystemEnv("XDG_DATA_HOME");
[11162]417 if (xdgDataDir != null && !xdgDataDir.isEmpty()) {
[14148]418 return new File(xdgDataDir, Preferences.getJOSMDirectoryBaseName());
[11162]419 } else {
[13647]420 return new File(getSystemProperty("user.home") + File.separator +
[14148]421 ".local" + File.separator + "share" + File.separator + Preferences.getJOSMDirectoryBaseName());
[11162]422 }
423 }
[7834]424 }
[8015]425
[11642]426 @Override
[13450]427 public X509Certificate getX509Certificate(NativeCertAmend certAmend)
[12241]428 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
[13693]429 for (String dir : new String[] {"/etc/ssl/certs", "/usr/share/ca-certificates/mozilla"}) {
430 File f = new File(dir, certAmend.getFilename());
431 if (f.exists()) {
432 CertificateFactory fact = CertificateFactory.getInstance("X.509");
433 try (InputStream is = Files.newInputStream(f.toPath())) {
434 return (X509Certificate) fact.generateCertificate(is);
435 }
[12241]436 }
437 }
438 return null;
439 }
[14144]440
441 @Override
442 public Collection<String> getPossiblePreferenceDirs() {
443 Set<String> locations = new HashSet<>();
444 locations.add("/usr/local/share/josm/");
445 locations.add("/usr/local/lib/josm/");
446 locations.add("/usr/share/josm/");
447 locations.add("/usr/lib/josm/");
448 return locations;
449 }
[1023]450}
Note: See TracBrowser for help on using the repository browser.