diff --git a/src/com/apple/OSXAdapter.java b/src/com/apple/OSXAdapter.java deleted file mode 100644 index a225b4717..000000000 --- a/src/com/apple/OSXAdapter.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - -File: OSXAdapter.java - -Abstract: Hooks existing preferences/about/quit functionality from an - existing Java app into handlers for the Mac OS X application menu. - Uses a Proxy object to dynamically implement the - com.apple.eawt.ApplicationListener interface and register it with the - com.apple.eawt.Application object. This allows the complete project - to be both built and run on any platform without any stubs or - placeholders. Useful for developers looking to implement Mac OS X - features while supporting multiple platforms with minimal impact. - -Version: 2.0 - -Disclaimer: IMPORTANT: This Apple software is supplied to you by -Apple Inc. ("Apple") in consideration of your agreement to the -following terms, and your use, installation, modification or -redistribution of this Apple software constitutes acceptance of these -terms. If you do not agree with these terms, please do not use, -install, modify or redistribute this Apple software. - -In consideration of your agreement to abide by the following terms, and -subject to these terms, Apple grants you a personal, non-exclusive -license, under Apple's copyrights in this original Apple software (the -"Apple Software"), to use, reproduce, modify and redistribute the Apple -Software, with or without modifications, in source and/or binary forms; -provided that if you redistribute the Apple Software in its entirety and -without modifications, you must retain this notice and the following -text and disclaimers in all such redistributions of the Apple Software. -Neither the name, trademarks, service marks or logos of Apple Inc. -may be used to endorse or promote products derived from the Apple -Software without specific prior written permission from Apple. Except -as expressly stated in this notice, no other rights or licenses, express -or implied, are granted by Apple herein, including but not limited to -any patent rights that may be infringed by your derivative works or by -other works in which the Apple Software may be incorporated. - -The Apple Software is provided by Apple on an "AS IS" basis. APPLE -MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION -THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND -OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - -IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL -OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, -MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED -AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), -STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -Copyright (C) 2003-2007 Apple, Inc., All Rights Reserved - -*/ - -package com.apple; -import java.lang.reflect.*; - - -public class OSXAdapter implements InvocationHandler { - - protected Object targetObject; - protected Method targetMethod; - protected String proxySignature; - - static Object macOSXApplication; - - // Pass this method an Object and Method equipped to perform application shutdown logic - // The method passed should return a boolean stating whether or not the quit should occur - public static void setQuitHandler(Object target, Method quitHandler) { - setHandler(new OSXAdapter("handleQuit", target, quitHandler)); - } - - // Pass this method an Object and Method equipped to display application info - // They will be called when the About menu item is selected from the application menu - public static void setAboutHandler(Object target, Method aboutHandler) { - boolean enableAboutMenu = (target != null && aboutHandler != null); - if (enableAboutMenu) { - setHandler(new OSXAdapter("handleAbout", target, aboutHandler)); - } - // If we're setting a handler, enable the About menu item by calling - // com.apple.eawt.Application reflectively - try { - Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[] { boolean.class }); - enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enableAboutMenu) }); - } catch (Exception ex) { - System.err.println("OSXAdapter could not access the About Menu"); - ex.printStackTrace(); - } - } - - // Pass this method an Object and a Method equipped to display application options - // They will be called when the Preferences menu item is selected from the application menu - public static void setPreferencesHandler(Object target, Method prefsHandler) { - boolean enablePrefsMenu = (target != null && prefsHandler != null); - if (enablePrefsMenu) { - setHandler(new OSXAdapter("handlePreferences", target, prefsHandler)); - } - // If we're setting a handler, enable the Preferences menu item by calling - // com.apple.eawt.Application reflectively - try { - Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[] { boolean.class }); - enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enablePrefsMenu) }); - } catch (Exception ex) { - System.err.println("OSXAdapter could not access the About Menu"); - ex.printStackTrace(); - } - } - - // Pass this method an Object and a Method equipped to handle document events from the Finder - // Documents are registered with the Finder via the CFBundleDocumentTypes dictionary in the - // application bundle's Info.plist - public static void setFileHandler(Object target, Method fileHandler) { - setHandler(new OSXAdapter("handleOpenFile", target, fileHandler) { - // Override OSXAdapter.callTarget to send information on the - // file to be opened - public boolean callTarget(Object appleEvent) { - if (appleEvent != null) { - try { - Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod("getFilename", (Class[])null); - String filename = (String) getFilenameMethod.invoke(appleEvent, (Object[])null); - this.targetMethod.invoke(this.targetObject, new Object[] { filename }); - } catch (Exception ex) { - - } - } - return true; - } - }); - } - - // setHandler creates a Proxy object from the passed OSXAdapter and adds it as an ApplicationListener - public static void setHandler(OSXAdapter adapter) { - try { - Class applicationClass = Class.forName("com.apple.eawt.Application"); - if (macOSXApplication == null) { - macOSXApplication = applicationClass.getConstructor((Class[])null).newInstance((Object[])null); - } - Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener"); - Method addListenerMethod = applicationClass.getDeclaredMethod("addApplicationListener", new Class[] { applicationListenerClass }); - // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener - Object osxAdapterProxy = Proxy.newProxyInstance(OSXAdapter.class.getClassLoader(), new Class[] { applicationListenerClass }, adapter); - addListenerMethod.invoke(macOSXApplication, new Object[] { osxAdapterProxy }); - } catch (ClassNotFoundException cnfe) { - System.err.println("This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled (" + cnfe + ")"); - } catch (Exception ex) { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods - System.err.println("Mac OS X Adapter could not talk to EAWT:"); - ex.printStackTrace(); - } - } - - // Each OSXAdapter has the name of the EAWT method it intends to listen for (handleAbout, for example), - // the Object that will ultimately perform the task, and the Method to be called on that Object - protected OSXAdapter(String proxySignature, Object target, Method handler) { - this.proxySignature = proxySignature; - this.targetObject = target; - this.targetMethod = handler; - } - - // Override this method to perform any operations on the event - // that comes with the various callbacks - // See setFileHandler above for an example - public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException { - Object result = targetMethod.invoke(targetObject, (Object[])null); - if (result == null) { - return true; - } - return Boolean.valueOf(result.toString()).booleanValue(); - } - - // InvocationHandler implementation - // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked - public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { - if (isCorrectMethod(method, args)) { - boolean handled = callTarget(args[0]); - setApplicationEventHandled(args[0], handled); - } - // All of the ApplicationListener methods are void; return null regardless of what happens - return null; - } - - // Compare the method that was called to the intended method when the OSXAdapter instance was created - // (e.g. handleAbout, handleQuit, handleOpenFile, etc.) - protected boolean isCorrectMethod(Method method, Object[] args) { - return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1); - } - - // It is important to mark the ApplicationEvent as handled and cancel the default behavior - // This method checks for a boolean result from the proxy method and sets the event accordingly - protected void setApplicationEventHandled(Object event, boolean handled) { - if (event != null) { - try { - Method setHandledMethod = event.getClass().getDeclaredMethod("setHandled", new Class[] { boolean.class }); - // If the target method returns a boolean, use that as a hint - setHandledMethod.invoke(event, new Object[] { Boolean.valueOf(handled) }); - } catch (Exception ex) { - System.err.println("OSXAdapter was unable to handle an ApplicationEvent: " + event); - ex.printStackTrace(); - } - } - } -} \ No newline at end of file diff --git a/src/com/apple/OSXAdapterWrapper.java b/src/com/apple/OSXAdapterWrapper.java deleted file mode 100644 index c7c0d95ce..000000000 --- a/src/com/apple/OSXAdapterWrapper.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @author Tres Finocchiaro - * - * Copyright (C) 2019 Tres Finocchiaro, QZ Industries, LLC - * - * LGPL 2.1 This is free software. This software and source code are released under - * the "LGPL 2.1 License". A copy of this license should be distributed with - * this software. http://www.gnu.org/licenses/lgpl-2.1.html - */ - -package com.apple; - -import com.github.zafarkhaja.semver.Version; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import qz.common.Constants; -import qz.utils.MacUtilities; - -import java.awt.*; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -/** - * Java 9+ compatible shim around Apple's OSXAdapter - * - * @author Tres Finocchiaro - */ -public class OSXAdapterWrapper implements InvocationHandler { - private static final Logger log = LogManager.getLogger(OSXAdapterWrapper.class); - public static final boolean legacyMode = Constants.JAVA_VERSION.lessThan(Version.valueOf("9.0.0")); - - private Object target; - private Method handler; - - public OSXAdapterWrapper(Object target, Method handler) { - this.target = target; - this.handler = handler; - } - - public static void setQuitHandler(Object target, Method handler) { - if (!legacyMode) { - // Java 9+ - wrap("java.awt.desktop.QuitHandler", "setQuitHandler", target, handler); - } else { - // Java 7, 8 - OSXAdapter.setAboutHandler(target, handler); - } - } - - public static void setAboutHandler(Object target, Method handler) { - if (!legacyMode) { - // Java 9+ - wrap("java.awt.desktop.AboutHandler", "setAboutHandler", target, handler); - } else { - // Java 7, 8 - OSXAdapter.setAboutHandler(target, handler); - } - } - - public static void wrap(String className, String methodName, Object target, Method handler) { - try { - Class desktop = Desktop.getDesktop().getClass(); - Class handlerClass = Class.forName(className); - Method method = desktop.getDeclaredMethod(methodName, new Class[] {handlerClass}); - Object proxy = Proxy.newProxyInstance(MacUtilities.class.getClassLoader(), new Class[] {handlerClass}, new OSXAdapterWrapper(target, handler)); - method.invoke(Desktop.getDesktop(), new Object[] {proxy}); - } catch (Exception e) { - log.warn("Failed to set {}", className, e.getMessage()); - } - } - - public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { - handler.invoke(target); - return null; - } -} diff --git a/src/qz/common/TrayManager.java b/src/qz/common/TrayManager.java index 3b98ab2a5..bf37efd69 100644 --- a/src/qz/common/TrayManager.java +++ b/src/qz/common/TrayManager.java @@ -350,6 +350,7 @@ private void addMenuItems() { componentList.add(aboutDialog); if (SystemUtilities.isMac()) { + MacUtilities.registerUriHandler(); MacUtilities.registerAboutDialog(aboutDialog); MacUtilities.registerQuitHandler(this); } diff --git a/src/qz/utils/MacUtilities.java b/src/qz/utils/MacUtilities.java index f86ab4be9..6dc3716d7 100644 --- a/src/qz/utils/MacUtilities.java +++ b/src/qz/utils/MacUtilities.java @@ -10,7 +10,6 @@ package qz.utils; -import com.apple.OSXAdapterWrapper; import org.apache.commons.io.FileUtils; import org.dyorgio.jna.platform.mac.*; import com.github.zafarkhaja.semver.Version; @@ -19,13 +18,14 @@ import org.apache.logging.log4j.Logger; import qz.common.Constants; import qz.common.TrayManager; +import qz.ui.AboutDialog; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.lang.reflect.Field; -import java.lang.reflect.Method; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -39,33 +39,42 @@ */ public class MacUtilities { private static final Logger log = LogManager.getLogger(MacUtilities.class); - private static Dialog aboutDialog; - private static TrayManager trayManager; private static String bundleId; private static Boolean jdkSupportsTemplateIcon; private static boolean templateIconForced = false; private static boolean sandboxed = System.getenv("APP_SANDBOX_CONTAINER_ID") != null; - public static void showAboutDialog() { - if (aboutDialog != null) { aboutDialog.setVisible(true); } - } - - public static void showExitPrompt() { - if (trayManager != null) { trayManager.exit(0); } - } - /** * Adds a listener to register the Apple "About" dialog to call {@code setVisible()} on the specified Dialog */ public static void registerAboutDialog(Dialog aboutDialog) { - MacUtilities.aboutDialog = aboutDialog; + Desktop.getDesktop().setAboutHandler(e -> { + if (aboutDialog != null) { + aboutDialog.setVisible(true); + } else { + log.warn("Ignoring request to show {} because it's null", AboutDialog.class.getSimpleName()); + } + }); + } - try { - OSXAdapterWrapper.setAboutHandler(MacUtilities.class, MacUtilities.class.getDeclaredMethod("showAboutDialog")); - } - catch(Exception e) { - e.printStackTrace(); - } + public static void registerUriHandler() { + Desktop.getDesktop().setOpenURIHandler(e -> { + // TODO: Not working. Why? + // TODO: Filter for "qz:launch" + URI uri = e.getURI(); + log.warn("Scheme: {}, Host: {}, Path: {}, ", uri.getScheme(), uri.getHost(), uri.getPath()); + + //if(uri.getScheme().equals(Constants.DATA_DIR)) { + //if(uri.getScheme().equals()) { + ShellUtilities.execute("/usr/bin/open", + // Open a new instance of the application even if one is already running. + "-n", + // Opens the specified application. + "-a", Constants.ABOUT_TITLE, + // All remaining arguments are passed to the opened application in the argv parameter to main(). + // These arguments are not opened or interpreted by the open tool. + "--args", ArgValue.STEAL.getMatch()); + }); } /** @@ -101,15 +110,15 @@ public static String getBundleId() { /** * Adds a listener to register the Apple "Quit" to call {@code trayManager.exit(0)} */ - public static void registerQuitHandler(TrayManager trayManager) { - MacUtilities.trayManager = trayManager; - - try { - OSXAdapterWrapper.setQuitHandler(MacUtilities.class, MacUtilities.class.getDeclaredMethod("showExitPrompt")); - } - catch(Exception e) { - e.printStackTrace(); - } + public static void registerQuitHandler(final TrayManager trayManager) { + Desktop.getDesktop().setQuitHandler((e, response) -> { + if (trayManager != null) { + trayManager.exit(0); + } else { + log.warn("{} is null, maybe we're running headless? Falling back to System.exit() instead.", TrayManager.class.getSimpleName()); + System.exit(0); + } + }); } /** @@ -126,25 +135,9 @@ public static boolean isDarkDesktop() { } public static int getScaleFactor() { - // Java 9+ per JDK-8172962 - if (Constants.JAVA_VERSION.greaterThanOrEqualTo(Version.valueOf("9.0.0"))) { - GraphicsDevice graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - GraphicsConfiguration graphicsConfig = graphicsDevice.getDefaultConfiguration(); - return (int)graphicsConfig.getDefaultTransform().getScaleX(); - } - // Java 7, 8 - try { - // Use reflection to avoid compile errors on non-macOS environments - Object screen = Class.forName("sun.awt.CGraphicsDevice").cast(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()); - Method getScaleFactor = screen.getClass().getDeclaredMethod("getScaleFactor"); - Object obj = getScaleFactor.invoke(screen); - if (obj instanceof Integer) { - return ((Integer)obj).intValue(); - } - } catch (Exception e) { - log.warn("Unable to determine screen scale factor. Defaulting to 1.", e); - } - return 1; + GraphicsDevice graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + GraphicsConfiguration graphicsConfig = graphicsDevice.getDefaultConfiguration(); + return (int)graphicsConfig.getDefaultTransform().getScaleX(); } /**