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

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

Java Warnings

  • Property svn:eol-style set to native
File size: 18.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.Utils.getSystemEnv;
6import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
7
8import java.awt.Desktop;
9import java.awt.event.KeyEvent;
10import java.io.BufferedReader;
11import java.io.File;
12import java.io.IOException;
13import java.io.InputStream;
14import java.net.URISyntaxException;
15import java.nio.charset.StandardCharsets;
16import java.nio.file.Files;
17import java.nio.file.Path;
18import java.nio.file.Paths;
19import java.security.KeyStoreException;
20import java.security.NoSuchAlgorithmException;
21import java.security.cert.CertificateException;
22import java.security.cert.CertificateFactory;
23import java.security.cert.X509Certificate;
24import java.util.Arrays;
25import java.util.Collection;
26import java.util.HashSet;
27import java.util.Locale;
28import java.util.Optional;
29import java.util.Set;
30import java.util.concurrent.ExecutionException;
31
32import org.openstreetmap.josm.data.Preferences;
33import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend;
34import org.openstreetmap.josm.spi.preferences.Config;
35
36/**
37 * {@code PlatformHook} implementation for Unix systems.
38 * @since 1023
39 */
40public class PlatformHookUnixoid implements PlatformHook {
41
42 private String osDescription;
43
44 @Override
45 public Platform getPlatform() {
46 return Platform.UNIXOID;
47 }
48
49 @Override
50 public void preStartupHook() {
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 }
62 }
63 }
64
65 @Override
66 public void openUrl(String url) throws IOException {
67 for (String program : Config.getPref().getList("browser.unix",
68 Arrays.asList("xdg-open", "#DESKTOP#", "$BROWSER", "gnome-open", "kfmclient openURL", "firefox"))) {
69 try {
70 if ("#DESKTOP#".equals(program)) {
71 Desktop.getDesktop().browse(Utils.urlToURI(url));
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 }
78 return;
79 } catch (IOException | URISyntaxException e) {
80 Logging.warn(e);
81 }
82 }
83 }
84
85 @Override
86 public void initSystemShortcuts() {
87 // CHECKSTYLE.OFF: LineLength
88 // TODO: Insert system shortcuts here. See Windows and especially OSX to see how to.
89 for (int i = KeyEvent.VK_F1; i <= KeyEvent.VK_F12; ++i) {
90 Shortcut.registerSystemShortcut("screen:toogle"+i, tr("reserved"), i, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
91 .setAutomatic();
92 }
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
98 }
99
100 @Override
101 public String getDefaultStyle() {
102 return "javax.swing.plaf.metal.MetalLookAndFeel";
103 }
104
105 /**
106 * Returns desktop environment based on the environment variable {@code XDG_CURRENT_DESKTOP}.
107 * @return desktop environment.
108 */
109 public Optional<String> getDesktopEnvironment() {
110 return Optional.ofNullable(getSystemEnv("XDG_CURRENT_DESKTOP")).filter(s -> !s.isEmpty());
111 }
112
113 /**
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);
121 } catch (IOException | ExecutionException | InterruptedException e) {
122 // lsb_release is not available on all Linux systems, so don't log at warning level
123 Logging.debug(e);
124 return false;
125 }
126 }
127
128 /**
129 * Get the package name including detailed version.
130 * @param packageNames The possible package names (when a package can have different names on different distributions)
131 * @return The package name and package version if it can be identified, null otherwise
132 * @since 7314
133 */
134 public static String getPackageDetails(String... packageNames) {
135 try {
136 // CHECKSTYLE.OFF: SingleSpaceSeparator
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();
140 // CHECKSTYLE.ON: SingleSpaceSeparator
141 if (dpkg || rpm || eque) {
142 for (String packageName : packageNames) {
143 String[] args;
144 if (dpkg) {
145 args = new String[] {"dpkg-query", "--show", "--showformat", "${Architecture}-${Version}", packageName};
146 } else if (eque) {
147 args = new String[] {"equery", "-q", "list", "-e", "--format=$fullversion", packageName};
148 } else {
149 args = new String[] {"rpm", "-q", "--qf", "%{arch}-%{version}", packageName};
150 }
151 try {
152 String version = Utils.execOutput(Arrays.asList(args));
153 if (version != null && !version.isEmpty()) {
154 return packageName + ':' + version;
155 }
156 } catch (ExecutionException e) {
157 // Package does not exist, continue
158 Logging.trace(e);
159 }
160 }
161 }
162 } catch (IOException | InterruptedException e) {
163 Logging.warn(e);
164 }
165 return null;
166 }
167
168 /**
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 *
174 * @return The package name and package version if it can be identified, null otherwise
175 */
176 public String getJavaPackageDetails() {
177 String home = getSystemProperty("java.home");
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")) {
181 return getPackageDetails("openjdk-9-jre", "java-1_9_0-openjdk", "java-1.9.0-openjdk", "java-9-openjdk");
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");
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");
192 }
193 return null;
194 }
195
196 /**
197 * Get the Web Start package name including detailed version.
198 *
199 * OpenJDK packages are shipped with icedtea-web package,
200 * but its version generally does not match main java package version.
201 *
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() {
207 if (isOpenJDK()) {
208 return getPackageDetails("icedtea-netx", "icedtea-web");
209 }
210 return null;
211 }
212
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
230 private String buildOSDescription() {
231 String osName = getSystemProperty("os.name");
232 if ("Linux".equalsIgnoreCase(osName)) {
233 try {
234 // Try lsb_release (only available on LSB-compliant Linux systems,
235 // see https://www.linuxbase.org/lsb-cert/productdir.php?by_prod )
236 String line = exec("lsb_release", "-ds");
237 if (line != null && !line.isEmpty()) {
238 line = line.replaceAll("\"+", "");
239 line = line.replace("NAME=", ""); // strange code for some Gentoo's
240 if (line.startsWith("Linux ")) // e.g. Linux Mint
241 return line;
242 else if (!line.isEmpty())
243 return "Linux " + line;
244 }
245 } catch (IOException e) {
246 Logging.debug(e);
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"),
255 new LinuxReleaseInfo("/etc/redhat-release"),
256 new LinuxReleaseInfo("/etc/SuSE-release")
257 }) {
258 String description = info.extractDescription();
259 if (description != null && !description.isEmpty()) {
260 return "Linux " + description;
261 }
262 }
263 }
264 }
265 return osName;
266 }
267
268 @Override
269 public String getOSDescription() {
270 if (osDescription == null) {
271 osDescription = buildOSDescription();
272 }
273 return osDescription;
274 }
275
276 private static class LinuxReleaseInfo {
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;
283
284 LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField) {
285 this(path, descriptionField, idField, releaseField, false, null);
286 }
287
288 LinuxReleaseInfo(String path) {
289 this(path, null, null, null, true, null);
290 }
291
292 LinuxReleaseInfo(String path, String prefix) {
293 this(path, null, null, null, true, prefix);
294 }
295
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
305 @Override
306 public String toString() {
307 return "ReleaseInfo [path=" + path + ", descriptionField=" + descriptionField +
308 ", idField=" + idField + ", releaseField=" + releaseField + ']';
309 }
310
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) {
318 Path p = Paths.get(path);
319 if (p.toFile().exists()) {
320 try (BufferedReader reader = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
321 String id = null;
322 String release = null;
323 String line;
324 while (result == null && (line = reader.readLine()) != null) {
325 if (line.contains("=")) {
326 String[] tokens = line.split("=", -1);
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) {
344 result = id + ' ' + release;
345 }
346 } catch (IOException e) {
347 // Ignore
348 Logging.trace(e);
349 }
350 }
351 }
352 // Append prefix if any
353 if (result != null && !result.isEmpty() && prefix != null && !prefix.isEmpty()) {
354 result = prefix + result;
355 }
356 if (result != null)
357 result = result.replaceAll("\"+", "");
358 return result;
359 }
360 }
361
362 /**
363 * Get the dot directory <code>~/.josm</code>.
364 * @return the dot directory
365 */
366 private static File getDotDirectory() {
367 String dirName = "." + Preferences.getJOSMDirectoryBaseName().toLowerCase(Locale.ENGLISH);
368 return new File(getSystemProperty("user.home"), dirName);
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 */
377 private static boolean useDotDirectory() {
378 return getDotDirectory().exists();
379 }
380
381 @Override
382 public File getDefaultCacheDirectory() {
383 if (useDotDirectory()) {
384 return new File(getDotDirectory(), "cache");
385 } else {
386 String xdgCacheDir = getSystemEnv("XDG_CACHE_HOME");
387 if (xdgCacheDir != null && !xdgCacheDir.isEmpty()) {
388 return new File(xdgCacheDir, Preferences.getJOSMDirectoryBaseName());
389 } else {
390 return new File(getSystemProperty("user.home") + File.separator +
391 ".cache" + File.separator + Preferences.getJOSMDirectoryBaseName());
392 }
393 }
394 }
395
396 @Override
397 public File getDefaultPrefDirectory() {
398 if (useDotDirectory()) {
399 return getDotDirectory();
400 } else {
401 String xdgConfigDir = getSystemEnv("XDG_CONFIG_HOME");
402 if (xdgConfigDir != null && !xdgConfigDir.isEmpty()) {
403 return new File(xdgConfigDir, Preferences.getJOSMDirectoryBaseName());
404 } else {
405 return new File(getSystemProperty("user.home") + File.separator +
406 ".config" + File.separator + Preferences.getJOSMDirectoryBaseName());
407 }
408 }
409 }
410
411 @Override
412 public File getDefaultUserDataDirectory() {
413 if (useDotDirectory()) {
414 return getDotDirectory();
415 } else {
416 String xdgDataDir = getSystemEnv("XDG_DATA_HOME");
417 if (xdgDataDir != null && !xdgDataDir.isEmpty()) {
418 return new File(xdgDataDir, Preferences.getJOSMDirectoryBaseName());
419 } else {
420 return new File(getSystemProperty("user.home") + File.separator +
421 ".local" + File.separator + "share" + File.separator + Preferences.getJOSMDirectoryBaseName());
422 }
423 }
424 }
425
426 @Override
427 public X509Certificate getX509Certificate(NativeCertAmend certAmend)
428 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
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 }
436 }
437 }
438 return null;
439 }
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 }
450}
Note: See TracBrowser for help on using the repository browser.