added cmd+, shortcut to macOS menubar

This commit is contained in:
Sebastian Stenzel
2017-05-27 01:20:44 +02:00
parent 96c2272b03
commit f4265e1d73
3 changed files with 132 additions and 51 deletions

View File

@@ -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<Path> 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<File> ff = (List<File>) 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;
}
}
}

View File

@@ -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"));
}

View File

@@ -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<EawtApplicationWrapper> 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<List<File>> 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<File> files = (List<File>) 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;
}
}
};
}
}