001package jmri.util;
002
003import java.awt.GraphicsConfiguration;
004import java.awt.GraphicsDevice;
005import java.awt.GraphicsEnvironment;
006import java.awt.HeadlessException;
007import java.awt.Insets;
008import java.awt.Toolkit;
009import java.io.BufferedReader;
010import java.io.File;
011import java.io.FileReader;
012import java.io.IOException;
013import java.io.InputStreamReader;
014import java.util.Arrays;
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018/**
019 * This class attempts to retrieve the screen insets for all operating systems.
020 * <p>
021 * The standard insets command fails on Linux - this class attempts to rectify
022 * that.
023 * <p>
024 * Borrows heavily from the <a href="https://community.oracle.com/thread/1368416?start=29">
025 * Linsets class created by: A. Tres Finocchiaro
026 * </a>
027 *
028 * @author Matt Harris
029 */
030public class JmriInsets {
031
032    private static final String DESKTOP_ENVIRONMENTS = "kdesktop|gnome-panel|xfce|darwin|icewm"; // NOI18N
033
034    private static final String GNOME_CONFIG = "%gconf.xml"; // NOI18N
035    private static final String GNOME_PANEL = "_panel_screen"; // NOI18N
036    private static final String GNOME_ROOT = System.getProperty("user.home") + "/.gconf/apps/panel/toplevels/"; // NOI18N
037
038    private static final String KDE_CONFIG = System.getProperty("user.home") + "/.kde/share/config/kickerrc"; // NOI18N
039
040    //private static final String XFCE_CONFIG = System.getProperty("user.home") + "/.config/xfce4/mcs_settings/panel.xml";
041    private static final String OS_NAME = SystemType.getOSName();
042
043    // Set this to -2 initially (out of the normal range)
044    // which can then be used to determine if we need to
045    // determine the window manager in use.
046    private static int linuxWM = -2;
047
048    /**
049     * Creates a new instance of JmriInsets
050     *
051     * @return the new instance
052     */
053    public static Insets getInsets() {
054
055        if (linuxWM == -2) {
056            linuxWM = getLinuxWindowManager();
057        }
058
059        switch (linuxWM) {
060            case 0:
061                return getKDEInsets();
062            case 1:
063                return getGnomeInsets();
064            case 2:
065                return getXfceInsets();
066            case 3:
067                return getDarwinInsets();
068            case 4:
069                return getIcewmInsets();
070            default:
071                return getDefaultInsets();
072        }
073
074    }
075
076    /*
077     * Determine the current Linux Window Manager
078     */
079    private static int getLinuxWindowManager() {
080        if (!SystemType.isWindows()
081                && !OS_NAME.toLowerCase().startsWith("mac")) { // NOI18N
082            try {
083                Process p = Runtime.getRuntime().exec(new String[]{"ps","ax"}); // NOI18N
084                BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
085
086                try {
087                    java.util.List<String> desktopList = Arrays.asList(DESKTOP_ENVIRONMENTS.split("\\|")); // NOI18N
088
089                    String line = r.readLine();
090                    while (line != null) {
091                        for (int i = 0; i < desktopList.size(); i++) {
092                            String s = desktopList.get(i);
093                            if (line.contains(s) && !line.contains("grep")) // NOI18N
094                            {
095                                return desktopList.indexOf(s);
096                            }
097                        }
098                        line = r.readLine();
099                    }
100                } catch (java.io.IOException e1) {
101                    log.error("error reading file", e1);
102                    throw e1;
103                } finally {
104                    try {
105                        r.close();
106                    } catch (java.io.IOException e2) {
107                        log.error("Exception closing file", e2);
108                    }
109                }
110
111            } catch (IOException e) {
112                log.error("IO Exception");
113            }
114        }
115        return -1;
116    }
117
118    /*
119     * Get insets for KDE 3.5+
120     */
121    private static Insets getKDEInsets() {
122        /*
123         * KDE:        2 Top          |  JAVA:        0 Top
124         *      0 Left       1 Right  |        1 Left       3 Right
125         *           3 Bottom         |             2 Bottom
126         */
127        int[] sizes = {24, 30, 46, 58, 0}; // xSmall, Small, Medium, Large, xLarge, Null
128        int[] i = {0, 0, 0, 0, 0};         // Left, Right, Top, Bottom, Null
129
130        /* Needs to be fixed. Doesn't know the difference between CustomSize and Size */
131        int iniCustomSize = getKdeINI("General", "CustomSize"); // NOI18N
132        int iniSize = getKdeINI("General", "Size"); // NOI18N
133        int iniPosition = getKdeINI("General", "Position"); // NOI18N
134        int position = iniPosition == -1 ? 3 : iniPosition;
135        int size = (iniCustomSize == -1 || iniSize != 4) ? iniSize : iniCustomSize;
136        size = size < 24 ? sizes[size] : size;
137        i[position] = size;
138        return new Insets(i[2], i[0], i[3], i[1]);
139    }
140
141    /*
142     * Get insets for Gnome
143     */
144    private static Insets getGnomeInsets() {
145        File gnomeRoot = new File(GNOME_ROOT);
146
147        int n = 0;
148        int s = 0;
149        int e = 0;
150        int w = 0;
151        File[] files = gnomeRoot.listFiles();
152        if (files != null) {
153            for (File f : files) {
154                String folder = f.getName();
155                if (f.isDirectory() && folder.contains(GNOME_PANEL)) {
156                    int val = getGnomeXML(new File(GNOME_ROOT + "/" + folder + "/" + GNOME_CONFIG));
157                    if (val == -1) {
158                        // Skip
159                    } else if (folder.startsWith("top" + GNOME_PANEL)) { // NOI18N
160                        n = Math.max(val, n);
161                    } else if (folder.startsWith("bottom" + GNOME_PANEL)) { // NOI18N
162                        s = Math.max(val, s);
163                    } else if (folder.startsWith("right" + GNOME_PANEL)) { // NOI18N
164                        e = Math.max(val, e);
165                    } else if (folder.startsWith("left" + GNOME_PANEL)) { // NOI18N
166                        w = Math.max(val, w);
167                    }
168                }
169            }
170        }
171        return new Insets(n, w, s, e);
172    }
173
174    /*
175     * Get insets for Xfce
176     */
177    private static Insets getXfceInsets() {
178        return getDefaultInsets(false);
179    }
180
181    /*
182     * Get insets for Darwin (Mac OS X)
183     */
184    private static Insets getDarwinInsets() {
185        return getDefaultInsets(false);
186    }
187
188    /*
189     * Get insets for IceWM
190     */
191    private static Insets getIcewmInsets() {
192        // OK, this is being a bit lazy but the vast majority of
193        // IceWM themes do not seem to modify the taskbar height
194        // from the default 25 pixels nor do they change the
195        // position of being along the bottom
196        return new Insets(0, 0, 25, 0);
197    }
198
199    /*
200     * Default insets (Java standard)
201     * Write log entry for any OS that we don't yet now how to handle.
202     */
203    private static Insets getDefaultInsets() {
204        if (!OS_NAME.toLowerCase().startsWith("windows") // NOI18N
205                && !OS_NAME.toLowerCase().startsWith("mac")) { // NOI18N
206            // MS Windows & Mac OS will always end-up here, so no need to log.
207            return getDefaultInsets(false);
208        } else {
209            // any other OS ends up here
210            return getDefaultInsets(true);
211        }
212    }
213
214    private static Insets getDefaultInsets(boolean logOS) {
215        if (logOS) {
216            log.trace("Trying default insets for {}", OS_NAME);
217        }
218        try {
219            GraphicsDevice gs[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
220            for (GraphicsDevice g : gs) {
221                GraphicsConfiguration[] gc = g.getConfigurations();
222                for (GraphicsConfiguration element : gc) {
223                    return (Toolkit.getDefaultToolkit().getScreenInsets(element));
224                }
225            }
226        } catch (HeadlessException h) {
227            log.debug("Warning: Headless error - no GUI available");
228        }
229        return new Insets(0, 0, 0, 0);
230    }
231
232    /*
233     * Some additional routines required for specific window managers
234     */
235 /*
236     * Parse XML files for some sizes in Gnome
237     */
238    private static int getGnomeXML(File xmlFile) {
239        try {
240            boolean found = false;
241            String temp;
242            try (FileReader reader = new FileReader(xmlFile); BufferedReader buffer = new BufferedReader(reader)) {
243                temp = buffer.readLine();
244                while (temp != null) {
245                    if (temp.contains("<entry name=\"size\"")) { // NOI18N
246                        found = true;
247                        break;
248                    }
249                    temp = buffer.readLine();
250                }
251            }
252            if (temp == null) {
253                return -1;
254            }
255            if (found) {
256                temp = temp.substring(temp.indexOf("value=\"") + 7);
257                return Integer.parseInt(temp.substring(0, temp.indexOf('"')));
258            }
259        } catch (IOException e) {
260            log.error("Error parsing Gnome XML: {}", e.getMessage());
261        }
262        return -1;
263    }
264
265    private static int getKdeINI(String category, String component) {
266        try {
267            File f = new File(KDE_CONFIG);
268            if (!f.exists() || category == null || component == null) {
269                return -1;
270            }
271
272            boolean found = false;
273            String value = null;
274            FileReader reader = new FileReader(f);
275            BufferedReader buffer = new BufferedReader(reader);
276            try {
277                String temp = buffer.readLine();
278                while (temp != null) {
279                    if (temp.trim().equals("[" + category + "]")) {
280                        temp = buffer.readLine();
281                        while (temp != null) {
282                            if (temp.trim().startsWith("[")) {
283                                return -1;
284                            } else if (temp.startsWith(component + "=")) {
285                                value = temp.substring(component.length() + 1);
286                                found = true;
287                                break;
288                            }
289                            temp = buffer.readLine();
290                        }
291                    }
292                    if (found == true) {
293                        break;
294                    }
295                    temp = buffer.readLine();
296                }
297            } catch (java.io.IOException e1) {
298                log.error("error reading file", e1);
299                throw e1;
300            } finally {
301                try {
302                    buffer.close();
303                } catch (java.io.IOException e2) {
304                    log.error("Exception closing file", e2);
305                }
306                try {
307                    reader.close();
308                } catch (java.io.IOException e2) {
309                    log.error("Exception closing file", e2);
310                }
311            }
312
313            if (found) {
314                return Integer.parseInt(value);
315            }
316        } catch (IOException e) {
317            log.error("Error parsing KDI_CONFIG: {}", e.getMessage());
318        }
319        return -1;
320    }
321
322    private final static Logger log = LoggerFactory.getLogger(JmriInsets.class);
323}