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

Last change on this file since 12936 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 17.1 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.event.KeyEvent;
8import java.io.BufferedReader;
9import java.io.File;
10import java.io.FileInputStream;
11import java.io.IOException;
12import java.net.URI;
13import java.net.URISyntaxException;
14import java.nio.charset.StandardCharsets;
15import java.nio.file.Files;
16import java.nio.file.Path;
17import java.nio.file.Paths;
18import java.security.KeyStoreException;
19import java.security.NoSuchAlgorithmException;
20import java.security.cert.CertificateException;
21import java.security.cert.CertificateFactory;
22import java.security.cert.X509Certificate;
23import java.util.Arrays;
24import java.util.Locale;
25import java.util.concurrent.ExecutionException;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.io.CertificateAmendment.CertAmend;
29import org.openstreetmap.josm.spi.preferences.Config;
30
31/**
32 * {@code PlatformHook} implementation for Unix systems.
33 * @since 1023
34 */
35public class PlatformHookUnixoid implements PlatformHook {
36
37 private String osDescription;
38
39 @Override
40 public Platform getPlatform() {
41 return Platform.UNIXOID;
42 }
43
44 @Override
45 public void preStartupHook() {
46 // See #12022 - Disable GNOME ATK Java wrapper as it causes a lot of serious trouble
47 if ("org.GNOME.Accessibility.AtkWrapper".equals(System.getProperty("assistive_technologies"))) {
48 System.clearProperty("assistive_technologies");
49 }
50 }
51
52 @Override
53 public void openUrl(String url) throws IOException {
54 for (String program : Config.getPref().getList("browser.unix",
55 Arrays.asList("xdg-open", "#DESKTOP#", "$BROWSER", "gnome-open", "kfmclient openURL", "firefox"))) {
56 try {
57 if ("#DESKTOP#".equals(program)) {
58 Desktop.getDesktop().browse(new URI(url));
59 } else if (program.startsWith("$")) {
60 program = System.getenv().get(program.substring(1));
61 Runtime.getRuntime().exec(new String[]{program, url});
62 } else {
63 Runtime.getRuntime().exec(new String[]{program, url});
64 }
65 return;
66 } catch (IOException | URISyntaxException e) {
67 Logging.warn(e);
68 }
69 }
70 }
71
72 @Override
73 public void initSystemShortcuts() {
74 // CHECKSTYLE.OFF: LineLength
75 // TODO: Insert system shortcuts here. See Windows and especially OSX to see how to.
76 for (int i = KeyEvent.VK_F1; i <= KeyEvent.VK_F12; ++i) {
77 Shortcut.registerSystemShortcut("screen:toogle"+i, tr("reserved"), i, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
78 .setAutomatic();
79 }
80 Shortcut.registerSystemShortcut("system:reset", tr("reserved"), KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
81 .setAutomatic();
82 Shortcut.registerSystemShortcut("system:resetX", tr("reserved"), KeyEvent.VK_BACK_SPACE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
83 .setAutomatic();
84 // CHECKSTYLE.ON: LineLength
85 }
86
87 @Override
88 public String getDefaultStyle() {
89 return "javax.swing.plaf.metal.MetalLookAndFeel";
90 }
91
92 /**
93 * Determines if the distribution is Debian or Ubuntu, or a derivative.
94 * @return {@code true} if the distribution is Debian, Ubuntu or Mint, {@code false} otherwise
95 */
96 public static boolean isDebianOrUbuntu() {
97 try {
98 String dist = Utils.execOutput(Arrays.asList("lsb_release", "-i", "-s"));
99 return "Debian".equalsIgnoreCase(dist) || "Ubuntu".equalsIgnoreCase(dist) || "Mint".equalsIgnoreCase(dist);
100 } catch (IOException | ExecutionException | InterruptedException e) {
101 // lsb_release is not available on all Linux systems, so don't log at warning level
102 Logging.debug(e);
103 return false;
104 }
105 }
106
107 /**
108 * Get the package name including detailed version.
109 * @param packageNames The possible package names (when a package can have different names on different distributions)
110 * @return The package name and package version if it can be identified, null otherwise
111 * @since 7314
112 */
113 public static String getPackageDetails(String... packageNames) {
114 try {
115 // CHECKSTYLE.OFF: SingleSpaceSeparator
116 boolean dpkg = Paths.get("/usr/bin/dpkg-query").toFile().exists();
117 boolean eque = Paths.get("/usr/bin/equery").toFile().exists();
118 boolean rpm = Paths.get("/bin/rpm").toFile().exists();
119 // CHECKSTYLE.ON: SingleSpaceSeparator
120 if (dpkg || rpm || eque) {
121 for (String packageName : packageNames) {
122 String[] args;
123 if (dpkg) {
124 args = new String[] {"dpkg-query", "--show", "--showformat", "${Architecture}-${Version}", packageName};
125 } else if (eque) {
126 args = new String[] {"equery", "-q", "list", "-e", "--format=$fullversion", packageName};
127 } else {
128 args = new String[] {"rpm", "-q", "--qf", "%{arch}-%{version}", packageName};
129 }
130 try {
131 String version = Utils.execOutput(Arrays.asList(args));
132 if (version != null && !version.isEmpty()) {
133 return packageName + ':' + version;
134 }
135 } catch (ExecutionException e) {
136 // Package does not exist, continue
137 Logging.trace(e);
138 }
139 }
140 }
141 } catch (IOException | InterruptedException e) {
142 Logging.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", "java-9-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 private 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 String line = exec("lsb_release", "-ds");
210 if (line != null && !line.isEmpty()) {
211 line = line.replaceAll("\"+", "");
212 line = line.replaceAll("NAME=", ""); // strange code for some Gentoo's
213 if (line.startsWith("Linux ")) // e.g. Linux Mint
214 return line;
215 else if (!line.isEmpty())
216 return "Linux " + line;
217 }
218 } catch (IOException e) {
219 Logging.debug(e);
220 // Non LSB-compliant Linux system. List of common fallback release files: http://linuxmafia.com/faq/Admin/release-files.html
221 for (LinuxReleaseInfo info : new LinuxReleaseInfo[]{
222 new LinuxReleaseInfo("/etc/lsb-release", "DISTRIB_DESCRIPTION", "DISTRIB_ID", "DISTRIB_RELEASE"),
223 new LinuxReleaseInfo("/etc/os-release", "PRETTY_NAME", "NAME", "VERSION"),
224 new LinuxReleaseInfo("/etc/arch-release"),
225 new LinuxReleaseInfo("/etc/debian_version", "Debian GNU/Linux "),
226 new LinuxReleaseInfo("/etc/fedora-release"),
227 new LinuxReleaseInfo("/etc/gentoo-release"),
228 new LinuxReleaseInfo("/etc/redhat-release"),
229 new LinuxReleaseInfo("/etc/SuSE-release")
230 }) {
231 String description = info.extractDescription();
232 if (description != null && !description.isEmpty()) {
233 return "Linux " + description;
234 }
235 }
236 }
237 }
238 return osName;
239 }
240
241 @Override
242 public String getOSDescription() {
243 if (osDescription == null) {
244 osDescription = buildOSDescription();
245 }
246 return osDescription;
247 }
248
249 private static class LinuxReleaseInfo {
250 private final String path;
251 private final String descriptionField;
252 private final String idField;
253 private final String releaseField;
254 private final boolean plainText;
255 private final String prefix;
256
257 LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField) {
258 this(path, descriptionField, idField, releaseField, false, null);
259 }
260
261 LinuxReleaseInfo(String path) {
262 this(path, null, null, null, true, null);
263 }
264
265 LinuxReleaseInfo(String path, String prefix) {
266 this(path, null, null, null, true, prefix);
267 }
268
269 private LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField, boolean plainText, String prefix) {
270 this.path = path;
271 this.descriptionField = descriptionField;
272 this.idField = idField;
273 this.releaseField = releaseField;
274 this.plainText = plainText;
275 this.prefix = prefix;
276 }
277
278 @Override
279 public String toString() {
280 return "ReleaseInfo [path=" + path + ", descriptionField=" + descriptionField +
281 ", idField=" + idField + ", releaseField=" + releaseField + ']';
282 }
283
284 /**
285 * Extracts OS detailed information from a Linux release file (/etc/xxx-release)
286 * @return The OS detailed information, or {@code null}
287 */
288 public String extractDescription() {
289 String result = null;
290 if (path != null) {
291 Path p = Paths.get(path);
292 if (p.toFile().exists()) {
293 try (BufferedReader reader = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
294 String id = null;
295 String release = null;
296 String line;
297 while (result == null && (line = reader.readLine()) != null) {
298 if (line.contains("=")) {
299 String[] tokens = line.split("=");
300 if (tokens.length >= 2) {
301 // Description, if available, contains exactly what we need
302 if (descriptionField != null && descriptionField.equalsIgnoreCase(tokens[0])) {
303 result = Utils.strip(tokens[1]);
304 } else if (idField != null && idField.equalsIgnoreCase(tokens[0])) {
305 id = Utils.strip(tokens[1]);
306 } else if (releaseField != null && releaseField.equalsIgnoreCase(tokens[0])) {
307 release = Utils.strip(tokens[1]);
308 }
309 }
310 } else if (plainText && !line.isEmpty()) {
311 // Files composed of a single line
312 result = Utils.strip(line);
313 }
314 }
315 // If no description has been found, try to rebuild it with "id" + "release" (i.e. "name" + "version")
316 if (result == null && id != null && release != null) {
317 result = id + ' ' + release;
318 }
319 } catch (IOException e) {
320 // Ignore
321 Logging.trace(e);
322 }
323 }
324 }
325 // Append prefix if any
326 if (result != null && !result.isEmpty() && prefix != null && !prefix.isEmpty()) {
327 result = prefix + result;
328 }
329 if (result != null)
330 result = result.replaceAll("\"+", "");
331 return result;
332 }
333 }
334
335 /**
336 * Get the dot directory <code>~/.josm</code>.
337 * @return the dot directory
338 */
339 private static File getDotDirectory() {
340 String dirName = "." + Main.pref.getJOSMDirectoryBaseName().toLowerCase(Locale.ENGLISH);
341 return new File(System.getProperty("user.home"), dirName);
342 }
343
344 /**
345 * Returns true if the dot directory should be used for storing preferences,
346 * cache and user data.
347 * Currently this is the case, if the dot directory already exists.
348 * @return true if the dot directory should be used
349 */
350 private static boolean useDotDirectory() {
351 return getDotDirectory().exists();
352 }
353
354 @Override
355 public File getDefaultCacheDirectory() {
356 if (useDotDirectory()) {
357 return new File(getDotDirectory(), "cache");
358 } else {
359 String xdgCacheDir = System.getenv("XDG_CACHE_HOME");
360 if (xdgCacheDir != null && !xdgCacheDir.isEmpty()) {
361 return new File(xdgCacheDir, Main.pref.getJOSMDirectoryBaseName());
362 } else {
363 return new File(System.getProperty("user.home") + File.separator +
364 ".cache" + File.separator + Main.pref.getJOSMDirectoryBaseName());
365 }
366 }
367 }
368
369 @Override
370 public File getDefaultPrefDirectory() {
371 if (useDotDirectory()) {
372 return getDotDirectory();
373 } else {
374 String xdgConfigDir = System.getenv("XDG_CONFIG_HOME");
375 if (xdgConfigDir != null && !xdgConfigDir.isEmpty()) {
376 return new File(xdgConfigDir, Main.pref.getJOSMDirectoryBaseName());
377 } else {
378 return new File(System.getProperty("user.home") + File.separator +
379 ".config" + File.separator + Main.pref.getJOSMDirectoryBaseName());
380 }
381 }
382 }
383
384 @Override
385 public File getDefaultUserDataDirectory() {
386 if (useDotDirectory()) {
387 return getDotDirectory();
388 } else {
389 String xdgDataDir = System.getenv("XDG_DATA_HOME");
390 if (xdgDataDir != null && !xdgDataDir.isEmpty()) {
391 return new File(xdgDataDir, Main.pref.getJOSMDirectoryBaseName());
392 } else {
393 return new File(System.getProperty("user.home") + File.separator +
394 ".local" + File.separator + "share" + File.separator + Main.pref.getJOSMDirectoryBaseName());
395 }
396 }
397 }
398
399 @Override
400 public X509Certificate getX509Certificate(CertAmend certAmend)
401 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
402 File f = new File("/usr/share/ca-certificates/mozilla", certAmend.getFilename());
403 if (f.exists()) {
404 CertificateFactory fact = CertificateFactory.getInstance("X.509");
405 try (FileInputStream is = new FileInputStream(f)) {
406 return (X509Certificate) fact.generateCertificate(is);
407 }
408 }
409 return null;
410 }
411}
Note: See TracBrowser for help on using the repository browser.