From f4265e1d7375536677d634d177934cf6bf3964ec Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 27 May 2017 01:20:44 +0200 Subject: [PATCH] added cmd+, shortcut to macOS menubar --- .../launcher/FileOpenRequestHandler.java | 55 +-------- .../ui/controllers/MainController.java | 23 +++- .../ui/util/EawtApplicationWrapper.java | 105 ++++++++++++++++++ 3 files changed, 132 insertions(+), 51 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java index fdaafe483..97306d231 100644 --- a/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java +++ b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java @@ -7,17 +7,14 @@ package org.cryptomator.launcher; import java.io.File; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.InvalidPathException; import java.nio.file.Path; -import java.util.List; import java.util.concurrent.BlockingQueue; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.util.EawtApplicationWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +26,11 @@ class FileOpenRequestHandler { public FileOpenRequestHandler(BlockingQueue fileOpenRequests) { this.fileOpenRequests = fileOpenRequests; if (SystemUtils.IS_OS_MAC_OSX) { - addOsxFileOpenHandler(); + EawtApplicationWrapper.getApplication().ifPresent(app -> { + app.setOpenFileHandler(files -> { + files.stream().map(File::toPath).forEach(fileOpenRequests::add); + }); + }); } } @@ -55,48 +56,4 @@ class FileOpenRequestHandler { } } - /** - * Event subscription code inspired by https://gitlab.com/axet/desktop/blob/master/java/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java - */ - private void addOsxFileOpenHandler() { - try { - final Class applicationClass = Class.forName("com.apple.eawt.Application"); - final Class openFilesHandlerClass = Class.forName("com.apple.eawt.OpenFilesHandler"); - final Method getApplication = applicationClass.getMethod("getApplication"); - final Object application = getApplication.invoke(null); - final Method setOpenFileHandler = applicationClass.getMethod("setOpenFileHandler", openFilesHandlerClass); - final ClassLoader openFilesHandlerClassLoader = openFilesHandlerClass.getClassLoader(); - final OpenFilesEventInvocationHandler openFilesHandler = new OpenFilesEventInvocationHandler(); - final Object openFilesHandlerObject = Proxy.newProxyInstance(openFilesHandlerClassLoader, new Class[] {openFilesHandlerClass}, openFilesHandler); - setOpenFileHandler.invoke(application, openFilesHandlerObject); - } catch (ReflectiveOperationException | RuntimeException e) { - // Since we're trying to call OS-specific code, we'll just have to hope for the best. - LOG.error("Exception adding OS X file open handler", e); - } - } - - /** - * Handler class inspired by https://gitlab.com/axet/desktop/blob/master/java/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java - */ - private class OpenFilesEventInvocationHandler implements InvocationHandler { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (method.getName().equals("openFiles")) { - final Class openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent"); - final Method getFiles = openFilesEventClass.getMethod("getFiles"); - Object e = args[0]; - try { - @SuppressWarnings("unchecked") - final List ff = (List) getFiles.invoke(e); - ff.stream().map(File::toPath).forEach(fileOpenRequests::add); - } catch (RuntimeException ee) { - throw ee; - } catch (Exception ee) { - throw new RuntimeException(ee); - } - } - return null; - } - } - } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java index ba15b47e1..7a41bf1b7 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java @@ -37,6 +37,7 @@ import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.model.VaultFactory; import org.cryptomator.ui.model.VaultList; import org.cryptomator.ui.util.DialogBuilderUtil; +import org.cryptomator.ui.util.EawtApplicationWrapper; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; import org.fxmisc.easybind.monadic.MonadicBinding; @@ -119,6 +120,12 @@ public class MainController implements ViewController { EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit); autoUnlocker.unlockAllSilently(); + + EawtApplicationWrapper.getApplication().ifPresent(app -> { + app.setPreferencesHandler(() -> { + Platform.runLater(this::toggleShowSettings); + }); + }); } @FXML @@ -328,10 +335,14 @@ public class MainController implements ViewController { @FXML private void didClickShowSettings(ActionEvent e) { + toggleShowSettings(); + } + + private void toggleShowSettings() { if (isShowingSettings.get()) { - activeController.set(viewControllerLoader.load("/fxml/welcome.fxml")); + showWelcomeView(); } else { - activeController.set(viewControllerLoader.load("/fxml/settings.fxml")); + showPreferencesView(); } vaultList.getSelectionModel().clearSelection(); } @@ -375,6 +386,14 @@ public class MainController implements ViewController { // Subcontroller for right panel // **************************************** + private void showWelcomeView() { + activeController.set(viewControllerLoader.load("/fxml/welcome.fxml")); + } + + private void showPreferencesView() { + activeController.set(viewControllerLoader.load("/fxml/settings.fxml")); + } + private void showNotFoundView() { activeController.set(viewControllerLoader.load("/fxml/notfound.fxml")); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java b/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java new file mode 100644 index 000000000..8c260fe6f --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE file. + *******************************************************************************/ +package org.cryptomator.ui.util; + +import java.io.File; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Reflection-based wrapper for com.apple.eawt.Application. + */ +public class EawtApplicationWrapper { + + private static final Logger LOG = LoggerFactory.getLogger(EawtApplicationWrapper.class); + + private final Class applicationClass; + private final Object application; + + private EawtApplicationWrapper() throws ReflectiveOperationException { + this.applicationClass = Class.forName("com.apple.eawt.Application"); + this.application = applicationClass.getMethod("getApplication").invoke(null); + } + + public static Optional getApplication() { + try { + return Optional.of(new EawtApplicationWrapper()); + } catch (ReflectiveOperationException e) { + return Optional.empty(); + } + } + + private void setOpenFileHandler(InvocationHandler handler) throws ReflectiveOperationException { + Class handlerClass = Class.forName("com.apple.eawt.OpenFilesHandler"); + Method setter = applicationClass.getMethod("setOpenFileHandler", handlerClass); + Object proxy = Proxy.newProxyInstance(applicationClass.getClassLoader(), new Class[] {handlerClass}, handler); + setter.invoke(application, proxy); + } + + public void setOpenFileHandler(Consumer> handler) { + try { + Class openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent"); + Method getFiles = openFilesEventClass.getMethod("getFiles"); + setOpenFileHandler(newMethodSpecificInvocationHandler("openFiles", args -> { + try { + Object openFilesEvent = args[0]; + @SuppressWarnings("unchecked") + List files = (List) getFiles.invoke(openFilesEvent); + handler.accept(files); + } catch (ReflectiveOperationException e) { + LOG.error("Error invoking openFileHandler.", e); + } + return null; + })); + } catch (ReflectiveOperationException e) { + LOG.error("Exception setting openFileHandler.", e); + } + } + + private void setPreferencesHandler(InvocationHandler handler) throws ReflectiveOperationException { + Class handlerClass = Class.forName("com.apple.eawt.PreferencesHandler"); + Method setter = applicationClass.getMethod("setPreferencesHandler", handlerClass); + Object proxy = Proxy.newProxyInstance(applicationClass.getClassLoader(), new Class[] {handlerClass}, handler); + setter.invoke(application, proxy); + } + + public void setPreferencesHandler(Runnable handler) { + try { + setPreferencesHandler(newMethodSpecificInvocationHandler("handlePreferences", args -> { + handler.run(); + return null; + })); + } catch (ReflectiveOperationException e) { + LOG.error("Exception setting preferencesHandler.", e); + } + } + + @FunctionalInterface + private static interface MethodSpecificInvocationHandler { + Object invoke(Object[] args); + } + + private static InvocationHandler newMethodSpecificInvocationHandler(String methodName, MethodSpecificInvocationHandler handler) { + return new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals(methodName)) { + return handler.invoke(args); + } else { + return null; + } + } + }; + } + +}