OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 144 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils;
15
 
80 ilm 16
import org.openconcerto.utils.OSFamily.Unix;
17
 
17 ilm 18
import java.io.ByteArrayOutputStream;
19
import java.io.File;
20
import java.io.IOException;
142 ilm 21
import java.io.InputStream;
17 ilm 22
import java.lang.reflect.Method;
93 ilm 23
import java.nio.charset.Charset;
180 ilm 24
import java.util.concurrent.FutureTask;
25
import java.util.concurrent.RunnableFuture;
142 ilm 26
import java.util.logging.Level;
17 ilm 27
import java.util.regex.Matcher;
28
import java.util.regex.Pattern;
29
 
180 ilm 30
import javax.swing.SwingUtilities;
17 ilm 31
import javax.swing.filechooser.FileSystemView;
142 ilm 32
import javax.xml.parsers.DocumentBuilder;
33
import javax.xml.parsers.DocumentBuilderFactory;
17 ilm 34
 
142 ilm 35
import org.w3c.dom.Element;
36
import org.w3c.dom.NodeList;
37
 
180 ilm 38
import net.jcip.annotations.GuardedBy;
39
import net.jcip.annotations.ThreadSafe;
40
 
17 ilm 41
/**
42
 * A desktop environment like Gnome or MacOS.
43
 *
44
 * @author Sylvain CUAZ
45
 * @see #getDE()
46
 */
180 ilm 47
@ThreadSafe
17 ilm 48
public abstract class DesktopEnvironment {
49
 
50
    static public final class Gnome extends DesktopEnvironment {
51
 
142 ilm 52
        static private final String getTextContent(final Element parentElem, final String childName) {
53
            final NodeList children = parentElem.getElementsByTagName(childName);
54
            if (children.getLength() != 1)
55
                throw new IllegalStateException("Not one child " + childName + " in " + parentElem);
56
            return children.item(0).getTextContent();
57
        }
58
 
59
        private final String name;
60
 
61
        public Gnome(final String name) {
62
            this.name = name;
63
        }
64
 
17 ilm 65
        @Override
66
        protected String findVersion() {
67
            try {
142 ilm 68
                if (this.name.equals("gnome")) {
69
                    // e.g. GNOME gnome-about 2.24.1
70
                    final String line = cmdSubstitution(Runtime.getRuntime().exec(new String[] { "gnome-about", "--version" }));
71
                    final String[] words = line.split(" ");
72
                    return words[words.length - 1];
73
                } else if (this.name.equals("gnome3")) {
74
                    final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
75
                    // <gnome-version>
76
                    // <platform>3</platform>
77
                    // <minor>18</minor>
78
                    // <micro>2</micro>
79
                    // </gnome-version>
80
                    final Element root = builder.parse(new File("/usr/share/gnome/gnome-version.xml")).getDocumentElement();
81
                    return getTextContent(root, "platform") + '.' + getTextContent(root, "minor") + '.' + getTextContent(root, "micro");
82
                } else {
83
                    throw new IllegalStateException("unknown name : " + this.name);
84
                }
85
            } catch (Exception e) {
86
                e.printStackTrace();
87
                return null;
88
            }
89
        }
90
    }
91
 
92
    static public final class MATE extends DesktopEnvironment {
93
 
94
        @Override
95
        protected String findVersion() {
96
            try {
17 ilm 97
                // e.g. GNOME gnome-about 2.24.1
142 ilm 98
                final String line = cmdSubstitution(Runtime.getRuntime().exec(new String[] { "mate-about", "--version" }));
17 ilm 99
                final String[] words = line.split(" ");
100
                return words[words.length - 1];
101
            } catch (Exception e) {
102
                e.printStackTrace();
103
                return null;
104
            }
105
        }
106
    }
107
 
108
    static public final class KDE extends DesktopEnvironment {
109
 
110
        private static final Pattern versionPattern = Pattern.compile("^KDE: (.*)$", Pattern.MULTILINE);
111
 
112
        @Override
113
        protected String findVersion() {
114
            try {
115
                // Qt: 3.3.8b
116
                // KDE: 3.5.10
117
                // kde-config: 1.0
118
                final String line = cmdSubstitution(Runtime.getRuntime().exec(new String[] { "kde-config", "--version" }));
119
                final Matcher matcher = versionPattern.matcher(line);
120
                matcher.find();
121
                return matcher.group(1);
122
            } catch (Exception e) {
123
                e.printStackTrace();
124
                return null;
125
            }
126
        }
127
    }
128
 
129
    static public final class XFCE extends DesktopEnvironment {
142 ilm 130
        // xfce4-about 4.11.1 (Xfce 4.10)
131
        // Copyright (c) 2008-2011
132
        private static final Pattern versionPattern = Pattern.compile("^xfce4-about.+\\(\\p{Alnum}+\\p{Blank}+(.+)\\)$", Pattern.MULTILINE);
17 ilm 133
 
134
        @Override
135
        protected String findVersion() {
142 ilm 136
            try {
137
                final String line = cmdSubstitution(Runtime.getRuntime().exec(new String[] { "xfce4-about", "--version" }));
138
                final Matcher matcher = versionPattern.matcher(line);
139
                matcher.find();
140
                return matcher.group(1);
141
            } catch (Exception e) {
142
                e.printStackTrace();
143
                return null;
144
            }
17 ilm 145
        }
146
    }
147
 
148
    static public final class Unknown extends DesktopEnvironment {
149
        @Override
150
        protected String findVersion() {
151
            return "";
152
        }
153
    }
154
 
155
    static private class DEisOS extends DesktopEnvironment {
156
        @Override
157
        protected String findVersion() {
158
            return System.getProperty("os.version");
159
        }
160
    }
161
 
162
    static public final class Windows extends DEisOS {
28 ilm 163
        static private boolean needsQuoting(String s) {
164
            final int len = s.length();
165
            if (len == 0) // empty string have to be quoted
166
                return true;
167
            for (int i = 0; i < len; i++) {
168
                switch (s.charAt(i)) {
93 ilm 169
                case ' ':
170
                case '\t':
171
                case '\\':
172
                case '"':
173
                    return true;
28 ilm 174
                }
175
            }
176
            return false;
177
        }
178
 
61 ilm 179
        // on Windows program themselves are required to parse the command line, thus a lot of them
180
        // do it differently, see http://www.autohotkey.net/~deleyd/parameters/parameters.htm
181
 
182
        static private final Pattern quotePatrn = Pattern.compile("([\\\\]*)\"");
183
        static private final Pattern endSlashPatrn = Pattern.compile("([\\\\]*)\\z");
184
 
28 ilm 185
        // see http://bugs.sun.com/view_bug.do?bug_id=6468220
61 ilm 186
        // e.g. find.exe, choice.exe
187
        public String quoteParamForMsftC(String s) {
28 ilm 188
            if (!needsQuoting(s))
189
                return s;
61 ilm 190
            if (s.length() > 0) {
191
                // replace '(\*)"' by '$1$1\"', e.g. '\quote " \"' by '\quote \" \\\"'
192
                // $1 needed so that the backslash we add isn't escaped itself by a preceding
193
                // backslash
194
                s = quotePatrn.matcher(s).replaceAll("$1$1\\\\\"");
195
                // replace '(\*)\z' by '$1$1', e.g. 'foo\' by 'foo\\'
196
                // needed to not escape closing quote
197
                s = endSlashPatrn.matcher(s).replaceAll("$1$1");
198
            }
199
            return '"' + s + '"';
200
        }
201
 
202
        // e.g. bash.exe
203
        public String quoteParamForGCC(String s) {
28 ilm 204
            return StringUtils.doubleQuote(s);
205
        }
61 ilm 206
 
207
        public String quoteParamForScript(final String s) {
208
            if (s.indexOf('"') >= 0)
209
                throw new IllegalArgumentException("Can not pass a double quote as part of a parameter");
210
            return '"' + s + '"';
211
        }
212
 
213
        @Override
214
        public String quoteParamForExec(final String s) {
215
            return quoteParamForMsftC(s);
216
        }
17 ilm 217
    }
218
 
219
    static public final class Mac extends DEisOS {
220
 
221
        // From CarbonCore/Folders.h
180 ilm 222
        public static final String kDocumentsDirectory = "docs";
223
        public static final String kPreferencesDirectory = "pref";
17 ilm 224
        private static Class<?> FileManagerClass;
225
        private static Short kUserDomain;
226
        private static Method OSTypeToInt;
227
 
228
        private static Class<?> getFileManagerClass() {
229
            if (FileManagerClass == null) {
230
                try {
231
                    FileManagerClass = Class.forName("com.apple.eio.FileManager");
232
                    OSTypeToInt = FileManagerClass.getMethod("OSTypeToInt", String.class);
233
                    kUserDomain = (Short) FileManagerClass.getField("kUserDomain").get(null);
234
                } catch (RuntimeException e) {
235
                    throw e;
236
                } catch (Exception e) {
237
                    throw new IllegalStateException(e);
238
                }
239
            }
240
            return FileManagerClass;
241
        }
242
 
243
        @Override
244
        public File getDocumentsFolder() {
180 ilm 245
            return new File(System.getProperty("user.home"), "Documents/");
17 ilm 246
        }
247
 
180 ilm 248
        // There was a warning until JRE v16, now "--add-exports
249
        // java.desktop/com.apple.eio=ALL-UNNAMED" is needed. Further this needs the EDT which is
250
        // undesirable just to get some paths.
251
        // https://bugs.openjdk.java.net/browse/JDK-8187981
252
        @Deprecated
17 ilm 253
        public File getFolder(String type) {
254
            try {
255
                final Method findFolder = getFileManagerClass().getMethod("findFolder", Short.TYPE, Integer.TYPE);
180 ilm 256
                final RunnableFuture<String> f = new FutureTask<>(() -> (String) findFolder.invoke(null, kUserDomain, OSTypeToInt.invoke(null, type)));
257
                // EDT needed at least for JRE v14-16 on macOS 10.15, otherwise the VM crashes :
258
                // Problematic frame: __NSAssertMainEventQueueIsCurrentEventQueue_block_invoke or
259
                // "The current event queue and the main event queue are not the same. This is
260
                // probably because _TSGetMainThread was called for the first time off the main
261
                // thread."
262
                if (SwingUtilities.isEventDispatchThread())
263
                    f.run();
264
                else
265
                    SwingUtilities.invokeLater(f);
266
                final String path = f.get();
17 ilm 267
                return new File(path);
268
            } catch (RuntimeException e) {
269
                throw e;
270
            } catch (Exception e) {
271
                throw new IllegalStateException(e);
272
            }
273
        }
274
 
61 ilm 275
        public File getAppDir(final String bundleID) throws IOException {
276
            // we used to ask for the URL of the application file but since 10.7 it returns a
277
            // file reference URL like "file:///.file/id=6723689.35865"
142 ilm 278
            final ProcessBuilder processBuilder = new ProcessBuilder("osascript", "-e",
279
                    "tell application id \"com.apple.Finder\" to POSIX path of (application file id \"" + bundleID + "\" as string)");
61 ilm 280
            // if not found prints nothing to out and a cryptic error to the standard error stream
281
            final String dir = cmdSubstitution(processBuilder.start()).trim();
282
            return dir.length() == 0 ? null : new File(dir);
283
        }
17 ilm 284
    }
285
 
286
    /**
287
     * Execute the passed command and test its return code.
288
     *
289
     * @param command the command to {@link Runtime#exec(String[]) execute}.
290
     * @return <code>false</code> if the {@link Process#waitFor() return code} is not 0 or an
291
     *         exception is thrown.
292
     * @throws RTInterruptedException if this is interrupted while waiting.
293
     */
294
    public static final boolean test(final String... command) throws RTInterruptedException {
295
        try {
296
            return Runtime.getRuntime().exec(command).waitFor() == 0;
297
        } catch (InterruptedException e) {
298
            throw new RTInterruptedException(e);
299
        } catch (IOException e) {
300
            Log.get().finer(e.getLocalizedMessage());
301
            return false;
302
        }
303
    }
304
 
305
    public final static String cmdSubstitution(Process p) throws IOException {
93 ilm 306
        return cmdSubstitution(p, null);
307
    }
308
 
309
    public final static String cmdSubstitution(final Process p, final Charset encoding) throws IOException {
17 ilm 310
        final ByteArrayOutputStream out = new ByteArrayOutputStream(100 * 1024);
144 ilm 311
        // some programs won't write anything until they read everything (e.g. powershell.exe)
312
        p.getOutputStream().close();
93 ilm 313
        p.getErrorStream().close();
17 ilm 314
        StreamUtils.copy(p.getInputStream(), out);
93 ilm 315
        p.getInputStream().close();
316
        return encoding == null ? out.toString() : out.toString(encoding.name());
17 ilm 317
    }
318
 
142 ilm 319
    private static final String detectXDG() {
320
        String res = null;
321
        InputStream scriptIns = null;
322
        try {
323
            scriptIns = DesktopEnvironment.class.getResourceAsStream("DesktopEnvironmentXDG.sh");
324
            final Process ps = new ProcessBuilder("sh").start();
325
            ps.getErrorStream().close();
326
            StreamUtils.copy(scriptIns, ps.getOutputStream());
327
            ps.getOutputStream().close();
328
            res = FileUtils.readUTF8(ps.getInputStream()).trim();
329
            ps.getInputStream().close();
330
            if (ps.waitFor() != 0)
331
                throw new IllegalStateException("Not OK : " + ps.exitValue());
332
        } catch (Exception e) {
333
            Log.get().fine(e.getLocalizedMessage());
334
        } finally {
335
            if (scriptIns != null) {
336
                try {
337
                    scriptIns.close();
338
                } catch (IOException e) {
339
                    e.printStackTrace();
340
                }
341
            }
342
        }
343
        return res;
344
    }
345
 
17 ilm 346
    private static final DesktopEnvironment detectDE() {
80 ilm 347
        final OSFamily os = OSFamily.getInstance();
348
        if (os == OSFamily.Windows) {
17 ilm 349
            return new Windows();
80 ilm 350
        } else if (os == OSFamily.Mac) {
17 ilm 351
            return new Mac();
80 ilm 352
        } else if (os instanceof Unix) {
142 ilm 353
            final String de = detectXDG();
354
            if (de.equals("xfce"))
355
                return new XFCE();
356
            else if (de.equals("mate"))
357
                return new MATE();
358
            else if (de.equals("kde"))
17 ilm 359
                return new KDE();
142 ilm 360
            else if (de.startsWith("gnome"))
361
                return new Gnome(de);
17 ilm 362
        }
363
        return new Unknown();
364
    }
365
 
180 ilm 366
    @GuardedBy("DesktopEnvironment.class")
17 ilm 367
    private static DesktopEnvironment DE = null;
368
 
180 ilm 369
    public synchronized static final DesktopEnvironment getDE() {
17 ilm 370
        if (DE == null) {
371
            DE = detectDE();
372
        }
373
        return DE;
374
    }
375
 
180 ilm 376
    public synchronized static final void resetDE() {
17 ilm 377
        DE = null;
378
    }
379
 
180 ilm 380
    @GuardedBy("this")
17 ilm 381
    private String version;
382
 
383
    private DesktopEnvironment() {
384
        this.version = null;
385
    }
386
 
387
    protected abstract String findVersion();
388
 
180 ilm 389
    public synchronized final String getVersion() {
17 ilm 390
        if (this.version == null)
391
            this.version = this.findVersion();
392
        return this.version;
393
    }
394
 
142 ilm 395
    // where to write user-visible files (e.g. spreadsheets, exports)
17 ilm 396
    public File getDocumentsFolder() {
397
        return FileSystemView.getFileSystemView().getDefaultDirectory();
398
    }
399
 
28 ilm 400
    // on some systems arguments are not passed correctly by ProcessBuilder
401
    public String quoteParamForExec(String s) {
402
        return s;
403
    }
404
 
17 ilm 405
    @Override
406
    public String toString() {
407
        return "DesktopEnvironment " + this.getClass().getSimpleName();
408
    }
409
 
410
    public static void main(String[] args) {
142 ilm 411
        // removes default
412
        LogUtils.rmRootHandlers();
413
        // add console handler
414
        LogUtils.setUpConsoleHandler();
415
        Log.get().setLevel(Level.FINE);
416
        final DesktopEnvironment de = getDE();
417
        System.out.println(de + " version " + de.getVersion());
17 ilm 418
    }
419
}