- minimizes to tray when vaults are still unlocked (i.e. webdav shares still mounted)

This commit is contained in:
Sebastian Stenzel
2014-12-11 00:27:44 +01:00
parent 5fe54634a9
commit b6546f24d5
6 changed files with 184 additions and 8 deletions
@@ -13,12 +13,14 @@ import java.util.ResourceBundle;
import java.util.Set;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.util.TrayIconUtil;
import org.eclipse.jetty.util.ConcurrentHashSet;
public class MainApplication extends Application {
@@ -27,30 +29,41 @@ public class MainApplication extends Application {
private static final CleanShutdownPerformer CLEAN_SHUTDOWN_PERFORMER = new CleanShutdownPerformer();
public static void main(String[] args) {
launch(args);
Application.launch(args);
Runtime.getRuntime().addShutdownHook(CLEAN_SHUTDOWN_PERFORMER);
}
@Override
public void start(final Stage primaryStage) throws IOException {
final ResourceBundle localizations = ResourceBundle.getBundle("localization");
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/main.fxml"), localizations);
final ResourceBundle rb = ResourceBundle.getBundle("localization");
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/main.fxml"), rb);
final Parent root = loader.load();
final MainController ctrl = loader.getController();
ctrl.setStage(primaryStage);
final Scene scene = new Scene(root);
primaryStage.setTitle("Cryptomator");
primaryStage.setTitle(rb.getString("app.name"));
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.setResizable(false);
primaryStage.show();
TrayIconUtil.init(primaryStage, rb, () -> {
quit();
});
}
private void quit() {
Platform.runLater(() -> {
CLEAN_SHUTDOWN_PERFORMER.run();
Settings.save();
Platform.exit();
System.exit(0);
});
}
@Override
public void stop() throws Exception {
public void stop() {
CLEAN_SHUTDOWN_PERFORMER.run();
Settings.save();
super.stop();
}
public static void addShutdownTask(Runnable r) {
@@ -11,8 +11,11 @@ package org.cryptomator.ui;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@@ -82,7 +85,7 @@ public class MainController implements Initializable, InitializationListener, Un
stage.setTitle(selectedDir.getName());
showDirectory(selectedDir);
}
private void showDirectory(Directory directory) {
try {
if (directory.isUnlocked()) {
@@ -133,6 +136,7 @@ public class MainController implements Initializable, InitializationListener, Un
@Override
public void didUnlock(UnlockController ctrl) {
showUnlockedView(ctrl.getDirectory());
Platform.setImplicitExit(false);
}
private void showUnlockedView(Directory directory) {
@@ -144,6 +148,19 @@ public class MainController implements Initializable, InitializationListener, Un
@Override
public void didLock(UnlockedController ctrl) {
showUnlockView(ctrl.getDirectory());
if (getUnlockedDirectories().isEmpty()) {
Platform.setImplicitExit(true);
}
}
/* Convenience */
public Collection<Directory> getDirectories() {
return directoryList.getItems();
}
public Collection<Directory> getUnlockedDirectories() {
return getDirectories().stream().filter(d -> d.isUnlocked()).collect(Collectors.toSet());
}
/* public Getter/Setter */
@@ -113,6 +113,23 @@ public class Directory implements Serializable {
return server;
}
/* hashcode/equals */
@Override
public int hashCode() {
return path.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Directory) {
final Directory other = (Directory) obj;
return this.path.equals(other.path);
} else {
return false;
}
}
/* graceful shutdown */
private class ShutdownTask implements Runnable {
@@ -0,0 +1,119 @@
package org.cryptomator.ui.util;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.TrayIcon.MessageType;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.stage.Stage;
import javax.swing.SwingUtilities;
import org.apache.commons.lang3.SystemUtils;
public final class TrayIconUtil {
private static TrayIconUtil INSTANCE;
private final Stage mainApplicationWindow;
private final ResourceBundle rb;
private final Runnable exitCommand;
/**
* This will add an icon to the system tray and modify the application shutdown procedure. Depending on
* {@link Platform#isImplicitExit()} the application may still be running, allowing shutdown using the tray menu.
*/
public synchronized static void init(Stage mainApplicationWindow, ResourceBundle rb, Runnable exitCommand) {
if (INSTANCE == null && SystemTray.isSupported()) {
INSTANCE = new TrayIconUtil(mainApplicationWindow, rb, exitCommand);
}
}
private TrayIconUtil(Stage mainApplicationWindow, ResourceBundle rb, Runnable exitCommand) {
this.mainApplicationWindow = mainApplicationWindow;
this.rb = rb;
this.exitCommand = exitCommand;
initTrayIcon();
}
private void initTrayIcon() {
final TrayIcon trayIcon = createTrayIcon();
try {
SystemTray.getSystemTray().add(trayIcon);
mainApplicationWindow.setOnCloseRequest((e) -> {
if (Platform.isImplicitExit()) {
exitCommand.run();
} else {
mainApplicationWindow.close();
this.showTrayNotification(trayIcon);
}
});
} catch (SecurityException | AWTException ex) {
// not working? then just go ahead and close the app
mainApplicationWindow.setOnCloseRequest((ev) -> {
exitCommand.run();
});
}
}
private TrayIcon createTrayIcon() {
final PopupMenu popup = new PopupMenu();
final MenuItem showItem = new MenuItem(rb.getString("tray.menu.open"));
showItem.addActionListener(this::restoreFromTray);
popup.add(showItem);
final MenuItem exitItem = new MenuItem(rb.getString("tray.menu.quit"));
exitItem.addActionListener(this::quitFromTray);
popup.add(exitItem);
final Image image = Toolkit.getDefaultToolkit().getImage(TrayIconUtil.class.getResource("/tray_icon.png"));
return new TrayIcon(image, rb.getString("app.name"), popup);
}
private void showTrayNotification(TrayIcon trayIcon) {
final Runnable notificationCmd;
if (SystemUtils.IS_OS_MAC_OSX) {
final String title = rb.getString("tray.infoMsg.title");
final String msg = rb.getString("tray.infoMsg.msg.osx");
final String notificationCenterAppleScript = String.format("display notification \"%s\" with title \"%s\"", msg, title);
notificationCmd = () -> {
try {
Runtime.getRuntime().exec(new String[] {"/usr/bin/osascript", "-e", notificationCenterAppleScript});
} catch (IOException e) {
// ignore, user will notice the tray icon anyway.
}
};
} else {
final String title = rb.getString("tray.infoMsg.title");
final String msg = rb.getString("tray.infoMsg.msg");
notificationCmd = () -> {
trayIcon.displayMessage(title, msg, MessageType.INFO);
};
}
SwingUtilities.invokeLater(() -> {
notificationCmd.run();
});
}
private void restoreFromTray(ActionEvent event) {
Platform.runLater(() -> {
mainApplicationWindow.show();
mainApplicationWindow.requestFocus();
});
}
private void quitFromTray(ActionEvent event) {
exitCommand.run();
}
}
@@ -7,6 +7,8 @@
# Sebastian Stenzel - initial API and implementation
#-------------------------------------------------------------------------------
app.name=Cryptomator
# welcome.fxml
welcome.welcomeLabel=Welcome to Cryptomator
@@ -34,4 +36,12 @@ unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
# unlocked.fxml
unlocked.messageLabel.runningOnPort=Vault is accessible via WebDAV on local port %d.
unlocked.button.lock=Lock vault
unlocked.button.lock=Lock vault
# tray icon
tray.menu.open=Open
tray.menu.quit=Quit
tray.infoMsg.title=Still running
tray.infoMsg.msg=Cryptomator is still alive. Quit it from the tray icon.
tray.infoMsg.msg.osx=Cryptomator is still alive. Quit it from the menu bar icon.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB