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

Last change on this file since 11809 was 11747, checked in by Don-vip, 7 years ago

checkstyle - NoWhiteSpaceBefore ...

  • Property svn:eol-style set to native
File size: 18.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Desktop;
7import java.awt.Dimension;
8import java.awt.event.KeyEvent;
9import java.io.BufferedReader;
10import java.io.File;
11import java.io.IOException;
12import java.io.InputStreamReader;
13import java.net.URI;
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.util.Arrays;
20import java.util.List;
21import java.util.Locale;
22
23import javax.swing.JOptionPane;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.gui.ExtendedDialog;
27import org.openstreetmap.josm.gui.util.GuiHelper;
28
29/**
30 * {@code PlatformHook} base implementation.
31 *
32 * Don't write (Main.platform instanceof PlatformHookUnixoid) because other platform
33 * hooks are subclasses of this class.
34 */
35public class PlatformHookUnixoid implements PlatformHook {
36
37 private String osDescription;
38
39 @Override
40 public void preStartupHook() {
41 // See #12022 - Disable GNOME ATK Java wrapper as it causes a lot of serious trouble
42 if ("org.GNOME.Accessibility.AtkWrapper".equals(System.getProperty("assistive_technologies"))) {
43 System.clearProperty("assistive_technologies");
44 }
45 }
46
47 @Override
48 public void openUrl(String url) throws IOException {
49 for (String program : Main.pref.getCollection("browser.unix",
50 Arrays.asList("xdg-open", "#DESKTOP#", "$BROWSER", "gnome-open", "kfmclient openURL", "firefox"))) {
51 try {
52 if ("#DESKTOP#".equals(program)) {
53 Desktop.getDesktop().browse(new URI(url));
54 } else if (program.startsWith("$")) {
55 program = System.getenv().get(program.substring(1));
56 Runtime.getRuntime().exec(new String[]{program, url});
57 } else {
58 Runtime.getRuntime().exec(new String[]{program, url});
59 }
60 return;
61 } catch (IOException | URISyntaxException e) {
62 Main.warn(e);
63 }
64 }
65 }
66
67 @Override
68 public void initSystemShortcuts() {
69 // CHECKSTYLE.OFF: LineLength
70 // TODO: Insert system shortcuts here. See Windows and especially OSX to see how to.
71 for (int i = KeyEvent.VK_F1; i <= KeyEvent.VK_F12; ++i) {
72 Shortcut.registerSystemShortcut("screen:toogle"+i, tr("reserved"), i, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
73 .setAutomatic();
74 }
75 Shortcut.registerSystemShortcut("system:reset", tr("reserved"), KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
76 .setAutomatic();
77 Shortcut.registerSystemShortcut("system:resetX", tr("reserved"), KeyEvent.VK_BACK_SPACE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
78 .setAutomatic();
79 // CHECKSTYLE.ON: LineLength
80 }
81
82 @Override
83 public String getDefaultStyle() {
84 return "javax.swing.plaf.metal.MetalLookAndFeel";
85 }
86
87 /**
88 * Determines if the distribution is Debian or Ubuntu, or a derivative.
89 * @return {@code true} if the distribution is Debian, Ubuntu or Mint, {@code false} otherwise
90 */
91 public static boolean isDebianOrUbuntu() {
92 try {
93 String dist = Utils.execOutput(Arrays.asList("lsb_release", "-i", "-s"));
94 return "Debian".equalsIgnoreCase(dist) || "Ubuntu".equalsIgnoreCase(dist) || "Mint".equalsIgnoreCase(dist);
95 } catch (IOException e) {
96 // lsb_release is not available on all Linux systems, so don't log at warning level
97 Main.debug(e);
98 return false;
99 }
100 }
101
102 /**
103 * Determines if the JVM is OpenJDK-based.
104 * @return {@code true} if {@code java.home} contains "openjdk", {@code false} otherwise
105 * @since 6951
106 */
107 public static boolean isOpenJDK() {
108 String javaHome = System.getProperty("java.home");
109 return javaHome != null && javaHome.contains("openjdk");
110 }
111
112 /**
113 * Get the package name including detailed version.
114 * @param packageNames The possible package names (when a package can have different names on different distributions)
115 * @return The package name and package version if it can be identified, null otherwise
116 * @since 7314
117 */
118 public static String getPackageDetails(String... packageNames) {
119 try {
120 // CHECKSTYLE.OFF: SingleSpaceSeparator
121 boolean dpkg = Paths.get("/usr/bin/dpkg-query").toFile().exists();
122 boolean eque = Paths.get("/usr/bin/equery").toFile().exists();
123 boolean rpm = Paths.get("/bin/rpm").toFile().exists();
124 // CHECKSTYLE.ON: SingleSpaceSeparator
125 if (dpkg || rpm || eque) {
126 for (String packageName : packageNames) {
127 String[] args;
128 if (dpkg) {
129 args = new String[] {"dpkg-query", "--show", "--showformat", "${Architecture}-${Version}", packageName};
130 } else if (eque) {
131 args = new String[] {"equery", "-q", "list", "-e", "--format=$fullversion", packageName};
132 } else {
133 args = new String[] {"rpm", "-q", "--qf", "%{arch}-%{version}", packageName};
134 }
135 String version = Utils.execOutput(Arrays.asList(args));
136 if (version != null && !version.contains("not installed")) {
137 return packageName + ':' + version;
138 }
139 }
140 }
141 } catch (IOException e) {
142 Main.warn(e);
143 }
144 return null;
145 }
146
147 /**
148 * Get the Java package name including detailed version.
149 *
150 * Some Java bugs are specific to a certain security update, so in addition
151 * to the Java version, we also need the exact package version.
152 *
153 * @return The package name and package version if it can be identified, null otherwise
154 */
155 public String getJavaPackageDetails() {
156 String home = System.getProperty("java.home");
157 if (home.contains("java-8-openjdk") || home.contains("java-1.8.0-openjdk")) {
158 return getPackageDetails("openjdk-8-jre", "java-1_8_0-openjdk", "java-1.8.0-openjdk");
159 } else if (home.contains("java-9-openjdk") || home.contains("java-1.9.0-openjdk")) {
160 return getPackageDetails("openjdk-9-jre", "java-1_9_0-openjdk", "java-1.9.0-openjdk");
161 } else if (home.contains("icedtea")) {
162 return getPackageDetails("icedtea-bin");
163 } else if (home.contains("oracle")) {
164 return getPackageDetails("oracle-jdk-bin", "oracle-jre-bin");
165 }
166 return null;
167 }
168
169 /**
170 * Get the Web Start package name including detailed version.
171 *
172 * OpenJDK packages are shipped with icedtea-web package,
173 * but its version generally does not match main java package version.
174 *
175 * Simply return {@code null} if there's no separate package for Java WebStart.
176 *
177 * @return The package name and package version if it can be identified, null otherwise
178 */
179 public String getWebStartPackageDetails() {
180 if (isOpenJDK()) {
181 return getPackageDetails("icedtea-netx", "icedtea-web");
182 }
183 return null;
184 }
185
186 /**
187 * Get the Gnome ATK wrapper package name including detailed version.
188 *
189 * Debian and Ubuntu derivatives come with a pre-enabled accessibility software
190 * completely buggy that makes Swing crash in a lot of different ways.
191 *
192 * Simply return {@code null} if it's not found.
193 *
194 * @return The package name and package version if it can be identified, null otherwise
195 */
196 public String getAtkWrapperPackageDetails() {
197 if (isOpenJDK() && isDebianOrUbuntu()) {
198 return getPackageDetails("libatk-wrapper-java");
199 }
200 return null;
201 }
202
203 protected String buildOSDescription() {
204 String osName = System.getProperty("os.name");
205 if ("Linux".equalsIgnoreCase(osName)) {
206 try {
207 // Try lsb_release (only available on LSB-compliant Linux systems,
208 // see https://www.linuxbase.org/lsb-cert/productdir.php?by_prod )
209 Process p = Runtime.getRuntime().exec("lsb_release -ds");
210 try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
211 String line = Utils.strip(input.readLine());
212 if (line != null && !line.isEmpty()) {
213 line = line.replaceAll("\"+", "");
214 line = line.replaceAll("NAME=", ""); // strange code for some Gentoo's
215 if (line.startsWith("Linux ")) // e.g. Linux Mint
216 return line;
217 else if (!line.isEmpty())
218 return "Linux " + line;
219 }
220 }
221 } catch (IOException e) {
222 Main.debug(e);
223 // Non LSB-compliant Linux system. List of common fallback release files: http://linuxmafia.com/faq/Admin/release-files.html
224 for (LinuxReleaseInfo info : new LinuxReleaseInfo[]{
225 new LinuxReleaseInfo("/etc/lsb-release", "DISTRIB_DESCRIPTION", "DISTRIB_ID", "DISTRIB_RELEASE"),
226 new LinuxReleaseInfo("/etc/os-release", "PRETTY_NAME", "NAME", "VERSION"),
227 new LinuxReleaseInfo("/etc/arch-release"),
228 new LinuxReleaseInfo("/etc/debian_version", "Debian GNU/Linux "),
229 new LinuxReleaseInfo("/etc/fedora-release"),
230 new LinuxReleaseInfo("/etc/gentoo-release"),
231 new LinuxReleaseInfo("/etc/redhat-release"),
232 new LinuxReleaseInfo("/etc/SuSE-release")
233 }) {
234 String description = info.extractDescription();
235 if (description != null && !description.isEmpty()) {
236 return "Linux " + description;
237 }
238 }
239 }
240 }
241 return osName;
242 }
243
244 @Override
245 public String getOSDescription() {
246 if (osDescription == null) {
247 osDescription = buildOSDescription();
248 }
249 return osDescription;
250 }
251
252 protected static class LinuxReleaseInfo {
253 private final String path;
254 private final String descriptionField;
255 private final String idField;
256 private final String releaseField;
257 private final boolean plainText;
258 private final String prefix;
259
260 public LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField) {
261 this(path, descriptionField, idField, releaseField, false, null);
262 }
263
264 public LinuxReleaseInfo(String path) {
265 this(path, null, null, null, true, null);
266 }
267
268 public LinuxReleaseInfo(String path, String prefix) {
269 this(path, null, null, null, true, prefix);
270 }
271
272 private LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField, boolean plainText, String prefix) {
273 this.path = path;
274 this.descriptionField = descriptionField;
275 this.idField = idField;
276 this.releaseField = releaseField;
277 this.plainText = plainText;
278 this.prefix = prefix;
279 }
280
281 @Override public String toString() {
282 return "ReleaseInfo [path=" + path + ", descriptionField=" + descriptionField +
283 ", idField=" + idField + ", releaseField=" + releaseField + ']';
284 }
285
286 /**
287 * Extracts OS detailed information from a Linux release file (/etc/xxx-release)
288 * @return The OS detailed information, or {@code null}
289 */
290 public String extractDescription() {
291 String result = null;
292 if (path != null) {
293 Path p = Paths.get(path);
294 if (p.toFile().exists()) {
295 try (BufferedReader reader = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
296 String id = null;
297 String release = null;
298 String line;
299 while (result == null && (line = reader.readLine()) != null) {
300 if (line.contains("=")) {
301 String[] tokens = line.split("=");
302 if (tokens.length >= 2) {
303 // Description, if available, contains exactly what we need
304 if (descriptionField != null && descriptionField.equalsIgnoreCase(tokens[0])) {
305 result = Utils.strip(tokens[1]);
306 } else if (idField != null && idField.equalsIgnoreCase(tokens[0])) {
307 id = Utils.strip(tokens[1]);
308 } else if (releaseField != null && releaseField.equalsIgnoreCase(tokens[0])) {
309 release = Utils.strip(tokens[1]);
310 }
311 }
312 } else if (plainText && !line.isEmpty()) {
313 // Files composed of a single line
314 result = Utils.strip(line);
315 }
316 }
317 // If no description has been found, try to rebuild it with "id" + "release" (i.e. "name" + "version")
318 if (result == null && id != null && release != null) {
319 result = id + ' ' + release;
320 }
321 } catch (IOException e) {
322 // Ignore
323 Main.trace(e);
324 }
325 }
326 }
327 // Append prefix if any
328 if (result != null && !result.isEmpty() && prefix != null && !prefix.isEmpty()) {
329 result = prefix + result;
330 }
331 if (result != null)
332 result = result.replaceAll("\"+", "");
333 return result;
334 }
335 }
336
337 // Method unused, but kept for translation already done. To reuse during Java 9 migration
338 protected void askUpdateJava(final String version, final String url) {
339 GuiHelper.runInEDTAndWait(() -> {
340 ExtendedDialog ed = new ExtendedDialog(
341 Main.parent,
342 tr("Outdated Java version"),
343 new String[]{tr("OK"), tr("Update Java"), tr("Cancel")});
344 // Check if the dialog has not already been permanently hidden by user
345 if (!ed.toggleEnable("askUpdateJava9").toggleCheckState()) {
346 ed.setButtonIcons(new String[]{"ok", "java", "cancel"}).setCancelButton(3);
347 ed.setMinimumSize(new Dimension(480, 300));
348 ed.setIcon(JOptionPane.WARNING_MESSAGE);
349 StringBuilder content = new StringBuilder(tr("You are running version {0} of Java.", "<b>"+version+"</b>"))
350 .append("<br><br>");
351 if ("Sun Microsystems Inc.".equals(System.getProperty("java.vendor")) && !isOpenJDK()) {
352 content.append("<b>").append(tr("This version is no longer supported by {0} since {1} and is not recommended for use.",
353 "Oracle", tr("April 2015"))).append("</b><br><br>"); // TODO: change date once Java 8 EOL is announced
354 }
355 content.append("<b>")
356 .append(tr("JOSM will soon stop working with this version; we highly recommend you to update to Java {0}.", "8"))
357 .append("</b><br><br>")
358 .append(tr("Would you like to update now ?"));
359 ed.setContent(content.toString());
360
361 if (ed.showDialog().getValue() == 2) {
362 try {
363 openUrl(url);
364 } catch (IOException e) {
365 Main.warn(e);
366 }
367 }
368 }
369 });
370 }
371
372 /**
373 * Get the dot directory <code>~/.josm</code>.
374 * @return the dot directory
375 */
376 private static File getDotDirectory() {
377 String dirName = "." + Main.pref.getJOSMDirectoryBaseName().toLowerCase(Locale.ENGLISH);
378 return new File(System.getProperty("user.home"), dirName);
379 }
380
381 /**
382 * Returns true if the dot directory should be used for storing preferences,
383 * cache and user data.
384 * Currently this is the case, if the dot directory already exists.
385 * @return true if the dot directory should be used
386 */
387 private static boolean useDotDirectory() {
388 return getDotDirectory().exists();
389 }
390
391 @Override
392 public File getDefaultCacheDirectory() {
393 if (useDotDirectory()) {
394 return new File(getDotDirectory(), "cache");
395 } else {
396 String xdgCacheDir = System.getenv("XDG_CACHE_HOME");
397 if (xdgCacheDir != null && !xdgCacheDir.isEmpty()) {
398 return new File(xdgCacheDir, Main.pref.getJOSMDirectoryBaseName());
399 } else {
400 return new File(System.getProperty("user.home") + File.separator +
401 ".cache" + File.separator + Main.pref.getJOSMDirectoryBaseName());
402 }
403 }
404 }
405
406 @Override
407 public File getDefaultPrefDirectory() {
408 if (useDotDirectory()) {
409 return getDotDirectory();
410 } else {
411 String xdgConfigDir = System.getenv("XDG_CONFIG_HOME");
412 if (xdgConfigDir != null && !xdgConfigDir.isEmpty()) {
413 return new File(xdgConfigDir, Main.pref.getJOSMDirectoryBaseName());
414 } else {
415 return new File(System.getProperty("user.home") + File.separator +
416 ".config" + File.separator + Main.pref.getJOSMDirectoryBaseName());
417 }
418 }
419 }
420
421 @Override
422 public File getDefaultUserDataDirectory() {
423 if (useDotDirectory()) {
424 return getDotDirectory();
425 } else {
426 String xdgDataDir = System.getenv("XDG_DATA_HOME");
427 if (xdgDataDir != null && !xdgDataDir.isEmpty()) {
428 return new File(xdgDataDir, Main.pref.getJOSMDirectoryBaseName());
429 } else {
430 return new File(System.getProperty("user.home") + File.separator +
431 ".local" + File.separator + "share" + File.separator + Main.pref.getJOSMDirectoryBaseName());
432 }
433 }
434 }
435
436 @Override
437 public List<File> getDefaultProj4NadshiftDirectories() {
438 return Arrays.asList(new File("/usr/local/share/proj"), new File("/usr/share/proj"));
439 }
440}
Note: See TracBrowser for help on using the repository browser.