001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Desktop;
007import java.awt.Image;
008import java.awt.Window;
009import java.awt.event.KeyEvent;
010import java.io.ByteArrayInputStream;
011import java.io.File;
012import java.io.IOException;
013import java.lang.reflect.InvocationHandler;
014import java.lang.reflect.InvocationTargetException;
015import java.lang.reflect.Method;
016import java.lang.reflect.Proxy;
017import java.nio.charset.StandardCharsets;
018import java.security.KeyStoreException;
019import java.security.NoSuchAlgorithmException;
020import java.security.cert.CertificateException;
021import java.security.cert.CertificateFactory;
022import java.security.cert.X509Certificate;
023import java.util.Arrays;
024import java.util.List;
025import java.util.Objects;
026import java.util.concurrent.ExecutionException;
027
028import javax.swing.UIManager;
029
030import org.openstreetmap.josm.Main;
031import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend;
032
033/**
034 * {@code PlatformHook} implementation for Apple Mac OS X systems.
035 * @since 1023
036 */
037public class PlatformHookOsx implements PlatformHook, InvocationHandler {
038
039    private String oSBuildNumber;
040
041    private NativeOsCallback osCallback;
042
043    @Override
044    public Platform getPlatform() {
045        return Platform.OSX;
046    }
047
048    @Override
049    public void preStartupHook() {
050        // This will merge our MenuBar into the system menu.
051        // MUST be set before Swing is initialized!
052        // And will not work when one of the system independent LAFs is used.
053        // They just insist on painting themselves...
054        Utils.updateSystemProperty("apple.laf.useScreenMenuBar", "true");
055        Utils.updateSystemProperty("apple.awt.application.name", "JOSM");
056    }
057
058    @Override
059    public void startupHook(JavaExpirationCallback callback) {
060        // Here we register callbacks for the menu entries in the system menu and file opening through double-click
061        // http://openjdk.java.net/jeps/272
062        // https://bugs.openjdk.java.net/browse/JDK-8048731
063        // http://cr.openjdk.java.net/~azvegint/jdk/9/8143227/10/jdk/
064        // This method must be cleaned up after we switch to Java 9
065        try {
066            Class<?> eawtApplication = Class.forName("com.apple.eawt.Application");
067            Class<?> quitHandler = findHandlerClass("QuitHandler");
068            Class<?> aboutHandler = findHandlerClass("AboutHandler");
069            Class<?> openFilesHandler = findHandlerClass("OpenFilesHandler");
070            Class<?> preferencesHandler = findHandlerClass("PreferencesHandler");
071            Object proxy = Proxy.newProxyInstance(PlatformHookOsx.class.getClassLoader(), new Class<?>[] {
072                quitHandler, aboutHandler, openFilesHandler, preferencesHandler}, this);
073            Object appli = eawtApplication.getConstructor((Class[]) null).newInstance((Object[]) null);
074            if (Utils.getJavaVersion() >= 9) {
075                setHandlers(Desktop.class, quitHandler, aboutHandler, openFilesHandler, preferencesHandler, proxy, Desktop.getDesktop());
076            } else {
077                setHandlers(eawtApplication, quitHandler, aboutHandler, openFilesHandler, preferencesHandler, proxy, appli);
078                // this method has been deprecated, but without replacement. To remove with Java 9 migration
079                eawtApplication.getDeclaredMethod("setEnabledPreferencesMenu", boolean.class).invoke(appli, Boolean.TRUE);
080            }
081            // setup the dock icon. It is automatically set with application bundle and Web start but we need
082            // to do it manually if run with `java -jar``
083            eawtApplication.getDeclaredMethod("setDockIconImage", Image.class).invoke(appli, ImageProvider.get("logo").getImage());
084            // enable full screen
085            enableOSXFullscreen((Window) Main.parent);
086        } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException ex) {
087            // We'll just ignore this for now. The user will still be able to close JOSM by closing all its windows.
088            Logging.warn("Failed to register with OSX: " + ex);
089        }
090        checkExpiredJava(callback);
091    }
092
093    @Override
094    public int getMenuShortcutKeyMaskEx() {
095        return KeyEvent.META_DOWN_MASK;
096    }
097
098    /**
099     * Registers Apple handlers.
100     * @param appClass application class
101     * @param quitHandler quit handler class
102     * @param aboutHandler about handler class
103     * @param openFilesHandler open file handler class
104     * @param preferencesHandler preferences handler class
105     * @param proxy proxy
106     * @param appInstance application instance (instance of {@code appClass})
107     * @throws IllegalAccessException in case of reflection error
108     * @throws InvocationTargetException in case of reflection error
109     * @throws NoSuchMethodException if any {@code set*Handler} method cannot be found
110     */
111    protected void setHandlers(Class<?> appClass, Class<?> quitHandler, Class<?> aboutHandler,
112            Class<?> openFilesHandler, Class<?> preferencesHandler, Object proxy, Object appInstance)
113                    throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
114        appClass.getDeclaredMethod("setQuitHandler", quitHandler).invoke(appInstance, proxy);
115        appClass.getDeclaredMethod("setAboutHandler", aboutHandler).invoke(appInstance, proxy);
116        appClass.getDeclaredMethod("setOpenFileHandler", openFilesHandler).invoke(appInstance, proxy);
117        appClass.getDeclaredMethod("setPreferencesHandler", preferencesHandler).invoke(appInstance, proxy);
118    }
119
120    /**
121     * Find Apple handler class in {@code com.apple.eawt} or {@code java.awt.desktop} packages.
122     * @param className simple class name
123     * @return class
124     * @throws ClassNotFoundException if the handler class cannot be found
125     */
126    protected Class<?> findHandlerClass(String className) throws ClassNotFoundException {
127        try {
128            // Java 8 handlers
129            return Class.forName("com.apple.eawt."+className);
130        } catch (ClassNotFoundException e) {
131            Logging.trace(e);
132            // Java 9 handlers
133            return Class.forName("java.awt.desktop."+className);
134        }
135    }
136
137    /**
138     * Enables fullscreen support for the given window.
139     * @param window The window for which full screen will be available
140     * @since 7482
141     */
142    public static void enableOSXFullscreen(Window window) {
143        CheckParameterUtil.ensureParameterNotNull(window, "window");
144        try {
145            // http://stackoverflow.com/a/8693890/2257172
146            Class<?> eawtFullScreenUtilities = Class.forName("com.apple.eawt.FullScreenUtilities");
147            eawtFullScreenUtilities.getDeclaredMethod("setWindowCanFullScreen",
148                    Window.class, boolean.class).invoke(eawtFullScreenUtilities, window, Boolean.TRUE);
149        } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) {
150            Logging.warn("Failed to register with OSX: " + e);
151        }
152    }
153
154    @Override
155    public void setNativeOsCallback(NativeOsCallback callback) {
156        osCallback = Objects.requireNonNull(callback);
157    }
158
159    @SuppressWarnings("unchecked")
160    @Override
161    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
162        if (Logging.isDebugEnabled()) {
163            Logging.debug("OSX handler: {0} - {1}", method.getName(), Arrays.toString(args));
164        }
165        switch (method.getName()) {
166        case "openFiles":
167            if (args[0] != null) {
168                try {
169                    Object oFiles = args[0].getClass().getMethod("getFiles").invoke(args[0]);
170                    if (oFiles instanceof List) {
171                        osCallback.openFiles((List<File>) oFiles);
172                    }
173                } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException ex) {
174                    Logging.warn("Failed to access open files event: " + ex);
175                }
176            }
177            break;
178        case "handleQuitRequestWith":
179            boolean closed = osCallback.handleQuitRequest();
180            if (args[1] != null) {
181                try {
182                    args[1].getClass().getDeclaredMethod(closed ? "performQuit" : "cancelQuit").invoke(args[1]);
183                } catch (IllegalAccessException e) {
184                    Logging.debug(e);
185                    // with Java 9, module java.desktop does not export com.apple.eawt, use new Desktop API instead
186                    Class.forName("java.awt.desktop.QuitResponse").getMethod(closed ? "performQuit" : "cancelQuit").invoke(args[1]);
187                }
188            }
189            break;
190        case "handleAbout":
191            osCallback.handleAbout();
192            break;
193        case "handlePreferences":
194            osCallback.handlePreferences();
195            break;
196        default:
197            Logging.warn("OSX unsupported method: "+method.getName());
198        }
199        return null;
200    }
201
202    @Override
203    public void openUrl(String url) throws IOException {
204        Runtime.getRuntime().exec("open " + url);
205    }
206
207    @Override
208    public void initSystemShortcuts() {
209        // CHECKSTYLE.OFF: LineLength
210        auto(Shortcut.registerSystemShortcut("apple-reserved-01", tr("reserved"), KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK)); // Show or hide the Spotlight search field (when multiple languages are installed, may rotate through enabled script systems).
211        auto(Shortcut.registerSystemShortcut("apple-reserved-02", tr("reserved"), KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Apple reserved.
212        auto(Shortcut.registerSystemShortcut("apple-reserved-03", tr("reserved"), KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Show the Spotlight search results window (when multiple languages are installed, may rotate through keyboard layouts and input methods within a script).
213        auto(Shortcut.registerSystemShortcut("apple-reserved-04", tr("reserved"), KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); //  | Apple reserved.
214        auto(Shortcut.registerSystemShortcut("apple-reserved-05", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK)); // Navigate through controls in a reverse direction. See "Keyboard Focus and Navigation."
215        auto(Shortcut.registerSystemShortcut("apple-reserved-06", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.META_DOWN_MASK)); // Move forward to the next most recently used application in a list of open applications.
216        auto(Shortcut.registerSystemShortcut("apple-reserved-07", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Move backward through a list of open applications (sorted by recent use).
217        auto(Shortcut.registerSystemShortcut("apple-reserved-08", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the next grouping of controls in a dialog or the next table (when Tab moves to the next cell). See Accessibility Overview.
218        auto(Shortcut.registerSystemShortcut("apple-reserved-09", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Move focus to the previous grouping of controls. See Accessibility Overview.
219        auto(Shortcut.registerSystemShortcut("apple-reserved-10", tr("reserved"), KeyEvent.VK_ESCAPE, KeyEvent.META_DOWN_MASK)); // Open Front Row.
220        auto(Shortcut.registerSystemShortcut("apple-reserved-11", tr("reserved"), KeyEvent.VK_ESCAPE, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Open the Force Quit dialog.
221        auto(Shortcut.registerSystemShortcut("apple-reserved-12", tr("reserved"), KeyEvent.VK_F1, KeyEvent.CTRL_DOWN_MASK)); // Toggle full keyboard access on or off. See Accessibility Overview.
222        auto(Shortcut.registerSystemShortcut("apple-reserved-13", tr("reserved"), KeyEvent.VK_F2, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the menu bar. See Accessibility Overview.
223        auto(Shortcut.registerSystemShortcut("apple-reserved-14", tr("reserved"), KeyEvent.VK_F3, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the Dock. See Accessibility Overview.
224        auto(Shortcut.registerSystemShortcut("apple-reserved-15", tr("reserved"), KeyEvent.VK_F4, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the active (or next) window. See Accessibility Overview.
225        auto(Shortcut.registerSystemShortcut("apple-reserved-16", tr("reserved"), KeyEvent.VK_F4, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Move focus to the previously active window. See Accessibility Overview.
226        auto(Shortcut.registerSystemShortcut("apple-reserved-17", tr("reserved"), KeyEvent.VK_F5, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the toolbar. See Accessibility Overview.
227        auto(Shortcut.registerSystemShortcut("apple-reserved-18", tr("reserved"), KeyEvent.VK_F5, KeyEvent.META_DOWN_MASK)); // Turn VoiceOver on or off. See Accessibility Overview.
228        auto(Shortcut.registerSystemShortcut("apple-reserved-19", tr("reserved"), KeyEvent.VK_F6, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the first (or next) panel. See Accessibility Overview.
229        auto(Shortcut.registerSystemShortcut("apple-reserved-20", tr("reserved"), KeyEvent.VK_F6, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Move focus to the previous panel. See Accessibility Overview.
230        auto(Shortcut.registerSystemShortcut("apple-reserved-21", tr("reserved"), KeyEvent.VK_F7, KeyEvent.CTRL_DOWN_MASK)); // Temporarily override the current keyboard access mode in windows and dialogs. See Accessibility Overview.
231        //auto(Shortcut.registerSystemShortcut("apple-reserved-22", tr("reserved"), KeyEvent.VK_F9, 0)); // Tile or untile all open windows.
232        //auto(Shortcut.registerSystemShortcut("apple-reserved-23", tr("reserved"), KeyEvent.VK_F10, 0)); // Tile or untile all open windows in the currently active application.
233        //auto(Shortcut.registerSystemShortcut("apple-reserved-24", tr("reserved"), KeyEvent.VK_F11, 0)); // Hide or show all open windows.
234        //auto(Shortcut.registerSystemShortcut("apple-reserved-25", tr("reserved"), KeyEvent.VK_F12, 0)); // Hide or display Dashboard.
235        auto(Shortcut.registerSystemShortcut("apple-reserved-26", tr("reserved"), KeyEvent.VK_DEAD_GRAVE, KeyEvent.META_DOWN_MASK)); // Activate the next open window in the frontmost application. See "Window Layering."
236        auto(Shortcut.registerSystemShortcut("apple-reserved-27", tr("reserved"), KeyEvent.VK_DEAD_GRAVE, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Activate the previous open window in the frontmost application. See "Window Layering."
237        auto(Shortcut.registerSystemShortcut("apple-reserved-28", tr("reserved"), KeyEvent.VK_DEAD_GRAVE, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Move focus to the window drawer.
238        //auto(Shortcut.registerSystemShortcut("apple-reserved-29", tr("reserved"), KeyEvent.VK_MINUS, KeyEvent.META_DOWN_MASK)); // Decrease the size of the selected item (equivalent to the Smaller command). See "The Format Menu."
239        auto(Shortcut.registerSystemShortcut("apple-reserved-30", tr("reserved"), KeyEvent.VK_MINUS, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Zoom out when screen zooming is on. See Accessibility Overview.
240
241        //Shortcut.registerSystemShortcut("system:align-left", tr("reserved"), KeyEvent.VK_OPEN_BRACKET, KeyEvent.META_DOWN_MASK); // Left-align a selection (equivalent to the Align Left command). See "The Format Menu."
242        //Shortcut.registerSystemShortcut("system:align-right",tr("reserved"), KeyEvent.VK_CLOSE_BRACKET, KeyEvent.META_DOWN_MASK); // Right-align a selection (equivalent to the Align Right command). See "The Format Menu."
243        // I found no KeyEvent for |
244        //Shortcut.registerSystemCut("system:align-center", tr("reserved"), '|', KeyEvent.META_DOWN_MASK); // Center-align a selection (equivalent to the Align Center command). See "The Format Menu."
245        //Shortcut.registerSystemShortcut("system:spelling", tr("reserved"), KeyEvent.VK_COLON, KeyEvent.META_DOWN_MASK); // Display the Spelling window (equivalent to the Spelling command). See "The Edit Menu."
246        //Shortcut.registerSystemShortcut("system:spellcheck", tr("reserved"), KeyEvent.VK_SEMICOLON, KeyEvent.META_DOWN_MASK); // Find misspelled words in the document (equivalent to the Check Spelling command). See "The Edit Menu."
247        auto(Shortcut.registerSystemShortcut("system:preferences", tr("reserved"), KeyEvent.VK_COMMA, KeyEvent.META_DOWN_MASK)); // Open the application's preferences window (equivalent to the Preferences command). See "The Application Menu."
248
249        auto(Shortcut.registerSystemShortcut("apple-reserved-31", tr("reserved"), KeyEvent.VK_COMMA, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Decrease screen contrast. See Accessibility Overview.
250        auto(Shortcut.registerSystemShortcut("apple-reserved-32", tr("reserved"), KeyEvent.VK_PERIOD, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Increase screen contrast. See Accessibility Overview.
251
252        // I found no KeyEvent for ?
253        //auto(Shortcut.registerSystemCut("system:help", tr("reserved"), '?', KeyEvent.META_DOWN_MASK)); // Open the application's help in Help Viewer. See "The Help Menu."
254
255        auto(Shortcut.registerSystemShortcut("apple-reserved-33", tr("reserved"), KeyEvent.VK_SLASH, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Turn font smoothing on or off.
256        auto(Shortcut.registerSystemShortcut("apple-reserved-34", tr("reserved"), KeyEvent.VK_EQUALS, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Increase the size of the selected item (equivalent to the Bigger command). See "The Format Menu."
257        auto(Shortcut.registerSystemShortcut("apple-reserved-35", tr("reserved"), KeyEvent.VK_EQUALS, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Zoom in when screen zooming is on. See Accessibility Overview.
258        auto(Shortcut.registerSystemShortcut("apple-reserved-36", tr("reserved"), KeyEvent.VK_3, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Capture the screen to a file.
259        auto(Shortcut.registerSystemShortcut("apple-reserved-37", tr("reserved"), KeyEvent.VK_3, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // Capture the screen to the Clipboard.
260        auto(Shortcut.registerSystemShortcut("apple-reserved-38", tr("reserved"), KeyEvent.VK_4, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Capture a selection to a file.
261        auto(Shortcut.registerSystemShortcut("apple-reserved-39", tr("reserved"), KeyEvent.VK_4, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // Capture a selection to the Clipboard.
262        auto(Shortcut.registerSystemShortcut("apple-reserved-40", tr("reserved"), KeyEvent.VK_8, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Turn screen zooming on or off. See Accessibility Overview.
263        auto(Shortcut.registerSystemShortcut("apple-reserved-41", tr("reserved"), KeyEvent.VK_8, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // Invert the screen colors. See Accessibility Overview.
264
265        Shortcut.registerSystemShortcut("system:selectall", tr("reserved"), KeyEvent.VK_A, KeyEvent.META_DOWN_MASK); // Highlight every item in a document or window, or all characters in a text field (equivalent to the Select All command). See "The Edit Menu."
266        //Shortcut.registerSystemShortcut("system:bold", tr("reserved"), KeyEvent.VK_B, KeyEvent.META_DOWN_MASK); // Boldface the selected text or toggle boldfaced text on and off (equivalent to the Bold command). See "The Edit Menu."
267        Shortcut.registerSystemShortcut("system:copy", tr("reserved"), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK); // Duplicate the selected data and store on the Clipboard (equivalent to the Copy command). See "The Edit Menu."
268        //Shortcut.registerSystemShortcut("system:colors", tr("reserved"), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display the Colors window (equivalent to the Show Colors command). See "The Format Menu."
269        //Shortcut.registerSystemShortcut("system:copystyle", tr("reserved"), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Copy the style of the selected text (equivalent to the Copy Style command). See "The Format Menu."
270        //Shortcut.registerSystemShortcut("system:copyformat", tr("reserved"), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // Copy the formatting settings of the selected item and store on the Clipboard (equivalent to the Copy Ruler command). See "The Format Menu."
271
272        auto(Shortcut.registerSystemShortcut("apple-reserved-42", tr("reserved"), KeyEvent.VK_D, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Show or hide the Dock. See "The Dock."
273
274        Shortcut.registerSystemShortcut("system:dictionarylookup", tr("reserved"), KeyEvent.VK_D, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK); // Display the definition of the selected word in the Dictionary application.
275        //Shortcut.registerSystemShortcut("system:findselected", tr("reserved"), KeyEvent.VK_E, KeyEvent.META_DOWN_MASK); // Use the selection for a find operation. See "Find Windows."
276        Shortcut.registerSystemShortcut("system:find", tr("reserved"), KeyEvent.VK_F, KeyEvent.META_DOWN_MASK); // Open a Find window (equivalent to the Find command). See "The Edit Menu."
277        Shortcut.registerSystemShortcut("system:search", tr("reserved"), KeyEvent.VK_F, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Jump to the search field control. See "Search Fields."
278        //Shortcut.registerSystemShortcut("system:findnext", tr("reserved"), KeyEvent.VK_G, KeyEvent.META_DOWN_MASK); // Find the next occurrence of the selection (equivalent to the Find Next command). See "The Edit Menu."
279        //Shortcut.registerSystemShortcut("system:findprev", tr("reserved"), KeyEvent.VK_G, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Find the previous occurrence of the selection (equivalent to the Find Previous command). See "The Edit Menu."
280        auto(Shortcut.registerSystemShortcut("system:hide", tr("reserved"), KeyEvent.VK_H, KeyEvent.META_DOWN_MASK)); // Hide the windows of the currently running application (equivalent to the Hide ApplicationName command). See "The Application Menu."
281        auto(Shortcut.registerSystemShortcut("system:hideothers", tr("reserved"), KeyEvent.VK_H, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Hide the windows of all other running applications (equivalent to the Hide Others command). See "The Application Menu."
282        // What about applications that have italic text AND info windows?
283        //Shortcut.registerSystemCut("system:italic", tr("reserved"), KeyEvent.VK_I, KeyEvent.META_DOWN_MASK); // Italicize the selected text or toggle italic text on or off (equivalent to the Italic command). See "The Format Menu."
284        //Shortcut.registerSystemShortcut("system:info", tr("reserved"), KeyEvent.VK_I, KeyEvent.META_DOWN_MASK); // Display an Info window. See "Inspector Windows."
285        //Shortcut.registerSystemShortcut("system:inspector", tr("reserved"), KeyEvent.VK_I, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Display an inspector window. See "Inspector Windows."
286        //Shortcut.registerSystemShortcut("system:toselection", tr("reserved"), KeyEvent.VK_J, KeyEvent.META_DOWN_MASK); // Scroll to a selection.
287        //Shortcut.registerSystemShortcut("system:minimize", tr("reserved"), KeyEvent.VK_M, KeyEvent.META_DOWN_MASK); // Minimize the active window to the Dock (equivalent to the Minimize command). See "The Window Menu."
288        //Shortcut.registerSystemShortcut("system:minimizeall", tr("reserved"), KeyEvent.VK_M, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Minimize all windows of the active application to the Dock (equivalent to the Minimize All command). See "The Window Menu."
289        Shortcut.registerSystemShortcut("system:new", tr("reserved"), KeyEvent.VK_N, KeyEvent.META_DOWN_MASK); // Open a new document (equivalent to the New command). See "The File Menu."
290        Shortcut.registerSystemShortcut("system:open", tr("reserved"), KeyEvent.VK_O, KeyEvent.META_DOWN_MASK); // Display a dialog for choosing a document to open (equivalent to the Open command). See "The File Menu."
291        Shortcut.registerSystemShortcut("system:print", tr("reserved"), KeyEvent.VK_P, KeyEvent.META_DOWN_MASK); // Display the Print dialog (equivalent to the Print command). See "The File Menu."
292        //Shortcut.registerSystemShortcut("system:printsetup", tr("reserved"), KeyEvent.VK_P, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display a dialog for specifying printing parameters (equivalent to the Page Setup command). See "The File Menu."
293        auto(Shortcut.registerSystemShortcut("system:menuexit", tr("reserved"), KeyEvent.VK_Q, KeyEvent.META_DOWN_MASK)); // Quit the application (equivalent to the Quit command). See "The Application Menu."
294
295        auto(Shortcut.registerSystemShortcut("apple-reserved-43", tr("reserved"), KeyEvent.VK_Q, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Log out the current user (equivalent to the Log Out command).
296        auto(Shortcut.registerSystemShortcut("apple-reserved-44", tr("reserved"), KeyEvent.VK_Q, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Log out the current user without confirmation.
297
298        Shortcut.registerSystemShortcut("system:save", tr("reserved"), KeyEvent.VK_S, KeyEvent.META_DOWN_MASK); // Save the active document (equivalent to the Save command). See "The File Menu."
299        Shortcut.registerSystemShortcut("system:saveas", tr("reserved"), KeyEvent.VK_S, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display the Save dialog (equivalent to the Save As command). See "The File Menu."
300        //Shortcut.registerSystemShortcut("system:fonts", tr("reserved"), KeyEvent.VK_T, KeyEvent.META_DOWN_MASK); // Display the Fonts window (equivalent to the Show Fonts command). See "The Format Menu."
301        Shortcut.registerSystemShortcut("system:toggletoolbar", tr("reserved"), KeyEvent.VK_T, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Show or hide a toolbar (equivalent to the Show/Hide Toolbar command). See "The View Menu" and "Toolbars."
302        //Shortcut.registerSystemShortcut("system:underline", tr("reserved"), KeyEvent.VK_U, KeyEvent.META_DOWN_MASK); // Underline the selected text or turn underlining on or off (equivalent to the Underline command). See "The Format Menu."
303        Shortcut.registerSystemShortcut("system:paste", tr("reserved"), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK); // Insert the Clipboard contents at the insertion point (equivalent to the Paste command). See "The File Menu."
304        //Shortcut.registerSystemShortcut("system:pastestyle", tr("reserved"), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Apply the style of one object to the selected object (equivalent to the Paste Style command). See "The Format Menu."
305        //Shortcut.registerSystemShortcut("system:pastemwithoutstyle", tr("reserved"), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Apply the style of the surrounding text to the inserted object (equivalent to the Paste and Match Style command). See "The Edit Menu."
306        //Shortcut.registerSystemShortcut("system:pasteformatting", tr("reserved"), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK); // Apply formatting settings to the selected object (equivalent to the Paste Ruler command). See "The Format Menu."
307        //Shortcut.registerSystemShortcut("system:closewindow", tr("reserved"), KeyEvent.VK_W, KeyEvent.META_DOWN_MASK); // Close the active window (equivalent to the Close command). See "The File Menu."
308        Shortcut.registerSystemShortcut("system:closefile", tr("reserved"), KeyEvent.VK_W, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Close a file and its associated windows (equivalent to the Close File command). See "The File Menu."
309        Shortcut.registerSystemShortcut("system:closeallwindows", tr("reserved"), KeyEvent.VK_W, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Close all windows in the application (equivalent to the Close All command). See "The File Menu."
310        Shortcut.registerSystemShortcut("system:cut", tr("reserved"), KeyEvent.VK_X, KeyEvent.META_DOWN_MASK); // Remove the selection and store on the Clipboard (equivalent to the Cut command). See "The Edit Menu."
311        Shortcut.registerSystemShortcut("system:undo", tr("reserved"), KeyEvent.VK_Z, KeyEvent.META_DOWN_MASK); // Reverse the effect of the user's previous operation (equivalent to the Undo command). See "The Edit Menu."
312        Shortcut.registerSystemShortcut("system:redo", tr("reserved"), KeyEvent.VK_Z, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Reverse the effect of the last Undo command (equivalent to the Redo command). See "The Edit Menu."
313
314        auto(Shortcut.registerSystemShortcut("apple-reserved-45", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.META_DOWN_MASK)); // Change the keyboard layout to current layout of Roman script.
315        //auto(Shortcut.registerSystemCut("apple-reserved-46", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the next semantic unit, typically the end of the current line.
316        //auto(Shortcut.registerSystemCut("apple-reserved-47", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection one character to the right.
317        //auto(Shortcut.registerSystemCut("apple-reserved-48", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the end of the current word, then to the end of the next word.
318
319        Shortcut.registerSystemShortcut("system:movefocusright", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview.
320
321        auto(Shortcut.registerSystemShortcut("apple-reserved-49", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.META_DOWN_MASK)); // Change the keyboard layout to current layout of system script.
322        //auto(Shortcut.registerSystemCut("apple-reserved-50", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the previous semantic unit, typically the beginning of the current line.
323        //auto(Shortcut.registerSystemCut("apple-reserved-51", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection one character to the left.
324        //auto(Shortcut.registerSystemCut("apple-reserved-52", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the beginning of the current word, then to the beginning of the previous word.
325
326        Shortcut.registerSystemShortcut("system:movefocusleft", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview.
327
328        //auto(Shortcut.registerSystemCut("apple-reserved-53", tr("reserved"), KeyEvent.VK_UP, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection upward in the next semantic unit, typically the beginning of the document.
329        //auto(Shortcut.registerSystemCut("apple-reserved-54", tr("reserved"), KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the line above, to the nearest character boundary at the same horizontal location.
330        //auto(Shortcut.registerSystemCut("apple-reserved-55", tr("reserved"), KeyEvent.VK_UP, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the beginning of the current paragraph, then to the beginning of the next paragraph.
331
332        Shortcut.registerSystemShortcut("system:movefocusup", tr("reserved"), KeyEvent.VK_UP, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview.
333
334        //auto(Shortcut.registerSystemCut("apple-reserved-56", tr("reserved"), KeyEvent.VK_DOWN, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection downward in the next semantic unit, typically the end of the document.
335        //auto(Shortcut.registerSystemCut("apple-reserved-57", tr("reserved"), KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the line below, to the nearest character boundary at the same horizontal location.
336        //auto(Shortcut.registerSystemCut("apple-reserved-58", tr("reserved"), KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the end of the current paragraph, then to the end of the next paragraph (include the blank line between paragraphs in cut, copy, and paste operations).
337
338        Shortcut.registerSystemShortcut("system:movefocusdown", tr("reserved"), KeyEvent.VK_DOWN, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview.
339
340        auto(Shortcut.registerSystemShortcut("system:about", tr("reserved"), 0, -1)); // About
341
342        Shortcut.registerSystemShortcut("view:zoomin", tr("reserved"), KeyEvent.VK_ADD, KeyEvent.META_DOWN_MASK); // Zoom in
343        Shortcut.registerSystemShortcut("view:zoomout", tr("reserved"), KeyEvent.VK_SUBTRACT, KeyEvent.META_DOWN_MASK); // Zoom out
344        // CHECKSTYLE.ON: LineLength
345    }
346
347    private static void auto(Shortcut sc) {
348        if (sc != null) {
349            sc.setAutomatic();
350        }
351    }
352
353    @Override
354    public String makeTooltip(String name, Shortcut sc) {
355        String lafid = UIManager.getLookAndFeel().getID();
356        boolean canHtml = true;
357        // "Mac" is the native LAF, "Aqua" is Quaqua. Both use native menus with native tooltips.
358        if (lafid.contains("Mac") || lafid.contains("Aqua")) {
359            canHtml = false;
360        }
361        StringBuilder result = new StringBuilder(48);
362        if (canHtml) {
363            result.append("<html>");
364        }
365        result.append(name);
366        if (sc != null && !sc.getKeyText().isEmpty()) {
367            result.append(' ');
368            if (canHtml) {
369                result.append("<font size='-2'>");
370            }
371            result.append('(').append(sc.getKeyText()).append(')');
372            if (canHtml) {
373                result.append("</font>");
374            }
375        }
376        if (canHtml) {
377            result.append("&nbsp;</html>");
378        }
379        return result.toString();
380    }
381
382    @Override
383    public String getDefaultStyle() {
384        return "com.apple.laf.AquaLookAndFeel";
385    }
386
387    @Override
388    public boolean canFullscreen() {
389        // OS X provides native full screen support registered at initialization, no need for custom action
390        return false;
391    }
392
393    @Override
394    public String getOSDescription() {
395        return System.getProperty("os.name") + ' ' + System.getProperty("os.version");
396    }
397
398    private String buildOSBuildNumber() {
399        StringBuilder sb = new StringBuilder();
400        try {
401            sb.append(exec("sw_vers", "-productName"))
402              .append(' ')
403              .append(exec("sw_vers", "-productVersion"))
404              .append(" (")
405              .append(exec("sw_vers", "-buildVersion"))
406              .append(')');
407        } catch (IOException e) {
408            Logging.error(e);
409        }
410        return sb.toString();
411    }
412
413    @Override
414    public String getOSBuildNumber() {
415        if (oSBuildNumber == null) {
416            oSBuildNumber = buildOSBuildNumber();
417        }
418        return oSBuildNumber;
419    }
420
421    @Override
422    public File getDefaultCacheDirectory() {
423        return new File(System.getProperty("user.home")+"/Library/Caches",
424                Main.pref.getJOSMDirectoryBaseName());
425    }
426
427    @Override
428    public File getDefaultPrefDirectory() {
429        return new File(System.getProperty("user.home")+"/Library/Preferences",
430                Main.pref.getJOSMDirectoryBaseName());
431    }
432
433    @Override
434    public File getDefaultUserDataDirectory() {
435        return new File(System.getProperty("user.home")+"/Library",
436                Main.pref.getJOSMDirectoryBaseName());
437    }
438
439    @Override
440    public X509Certificate getX509Certificate(NativeCertAmend certAmend)
441            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
442        try {
443            // Get platform certificate in PEM format
444            String pem = Utils.execOutput(Arrays.asList("security", "find-certificate",
445                    "-c", certAmend.getMacAlias(), "-p", "/System/Library/Keychains/SystemRootCertificates.keychain"));
446            Logging.debug(pem);
447            return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
448                    new ByteArrayInputStream(pem.getBytes(StandardCharsets.UTF_8)));
449        } catch (ExecutionException | InterruptedException | IllegalArgumentException e) {
450            throw new IOException(e);
451        }
452    }
453}