Add AppindicatorTrayMenuController

This commit is contained in:
Ralph Plawetzki
2023-04-08 16:22:27 +02:00
parent aa03bd119a
commit 6da107f4db
10 changed files with 179 additions and 15 deletions

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;~/.config/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;~/.config/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,libappindicator.gtk3.java.minimal" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;~/.config/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;~/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;~/.config/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;~/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;~/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;~/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;~/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,libappindicator.gtk3.java.minimal" />
<method v="2">
<option name="Make" enabled="true" />
</method>

10
pom.xml
View File

@@ -34,7 +34,7 @@
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.6.2</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.2.0</cryptomator.integrations.version>
<cryptomator.integrations.version>1.3.0-SNAPSHOT</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.2.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.0</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.2.0</cryptomator.integrations.linux.version>
@@ -55,6 +55,7 @@
<slf4j.version>2.0.6</slf4j.version>
<tinyoauth2.version>0.5.1</tinyoauth2.version>
<zxcvbn.version>1.7.0</zxcvbn.version>
<appindicator.version>1.1.0</appindicator.version>
<!-- test dependencies -->
<junit.jupiter.version>5.9.2</junit.jupiter.version>
@@ -250,6 +251,13 @@
<version>${jetbrains.annotations.version}</version>
<scope>provided</scope>
</dependency>
<!-- Java bindings for libappindicator -->
<dependency>
<groupId>org.purejava</groupId>
<artifactId>libappindicator-gtk3-java-minimal</artifactId>
<version>${appindicator.version}</version>
</dependency>
</dependencies>
<build>

View File

@@ -1,6 +1,7 @@
import ch.qos.logback.classic.spi.Configurator;
import org.cryptomator.integrations.tray.TrayMenuController;
import org.cryptomator.logging.LogbackConfiguratorFactory;
import org.cryptomator.ui.traymenu.AppindicatorTrayMenuController;
import org.cryptomator.ui.traymenu.AwtTrayMenuController;
open module org.cryptomator.desktop {
@@ -31,12 +32,13 @@ open module org.cryptomator.desktop {
requires com.tobiasdiez.easybind;
requires dagger;
requires io.github.coffeelibs.tinyoauth2client;
requires libappindicator.gtk3.java.minimal;
requires org.slf4j;
requires org.apache.commons.lang3;
/* TODO: filename-based modules: */
requires static javax.inject; /* ugly dagger/guava crap */
provides TrayMenuController with AwtTrayMenuController;
provides TrayMenuController with AwtTrayMenuController, AppindicatorTrayMenuController;
provides Configurator with LogbackConfiguratorFactory;
}

View File

@@ -0,0 +1,21 @@
package org.cryptomator.ui.traymenu;
import org.cryptomator.integrations.tray.ActionItem;
import org.purejava.linux.GCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ActionItemCallback implements GCallback {
private static final Logger LOG = LoggerFactory.getLogger(ActionItemCallback.class);
private ActionItem actionItem;
public ActionItemCallback(ActionItem actionItem) {
this.actionItem = actionItem;
}
@Override
public void apply() {
LOG.debug("Hit tray menu action '{}'", actionItem.title());
actionItem.action().run();
}
}

View File

@@ -0,0 +1,110 @@
package org.cryptomator.ui.traymenu;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.tray.ActionItem;
import org.cryptomator.integrations.tray.SeparatorItem;
import org.cryptomator.integrations.tray.SubMenuItem;
import org.cryptomator.integrations.tray.TrayMenuController;
import org.cryptomator.integrations.tray.TrayMenuException;
import org.cryptomator.integrations.tray.TrayMenuItem;
import org.purejava.linux.MemoryAllocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.lang.foreign.MemoryAddress;
import java.lang.foreign.MemorySession;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.List;
import static org.purejava.linux.app_indicator_h.*;
@CheckAvailability
public class AppindicatorTrayMenuController implements TrayMenuController {
private static final Logger LOG = LoggerFactory.getLogger(AppindicatorTrayMenuController.class);
private final MemorySession session = MemorySession.openShared();
private MemoryAddress indicator;
private MemoryAddress menu = gtk_menu_new();
@CheckAvailability
public static boolean isAvailable() {
return SystemUtils.IS_OS_LINUX && MemoryAllocator.isLoadedNativeLib();
}
@Override
public void showTrayIcon(URI uri, Runnable runnable, String s) throws TrayMenuException {
indicator = app_indicator_new(MemoryAllocator.ALLOCATE_FOR("org.cryptomator.Cryptomator"),
MemoryAllocator.ALLOCATE_FOR(getAbsolutePath(getPathString(uri))),
APP_INDICATOR_CATEGORY_APPLICATION_STATUS());
gtk_widget_show_all(menu);
app_indicator_set_menu(indicator, menu);
app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE());
}
@Override
public void updateTrayIcon(URI uri) {
app_indicator_set_icon(indicator, MemoryAllocator.ALLOCATE_FOR(getAbsolutePath(getPathString(uri))));
}
@Override
public void updateTrayMenu(List<TrayMenuItem> items) throws TrayMenuException {
menu = gtk_menu_new();
addChildren(menu, items);
gtk_widget_show_all(menu);
app_indicator_set_menu(indicator, menu);
}
@Override
public void onBeforeOpenMenu(Runnable runnable) {
}
private void addChildren(MemoryAddress menu, List<TrayMenuItem> items) {
for (var item : items) {
// TODO: use Pattern Matching for switch, once available
if (item instanceof ActionItem a) {
var gtkMenuItem = gtk_menu_item_new();
gtk_menu_item_set_label(gtkMenuItem, MemoryAllocator.ALLOCATE_FOR(a.title()));
g_signal_connect_object(gtkMenuItem,
MemoryAllocator.ALLOCATE_FOR("activate"),
MemoryAllocator.ALLOCATE_CALLBACK_FOR(new ActionItemCallback(a), session),
menu,
0);
gtk_menu_shell_append(menu, gtkMenuItem);
} else if (item instanceof SeparatorItem) {
var gtkSeparator = gtk_menu_item_new();
gtk_menu_shell_append(menu, gtkSeparator);
} else if (item instanceof SubMenuItem s) {
var gtkMenuItem = gtk_menu_item_new();
var gtkSubmenu = gtk_menu_new();
gtk_menu_item_set_label(gtkMenuItem, MemoryAllocator.ALLOCATE_FOR(s.title()));
addChildren(gtkSubmenu, s.items());
gtk_menu_item_set_submenu(gtkMenuItem, gtkSubmenu);
gtk_menu_shell_append(menu, gtkMenuItem);
}
gtk_widget_show_all(menu);
}
}
private String getAbsolutePath(String iconName) {
var res = getClass().getClassLoader().getResource(iconName);
if (null == res) {
throw new IllegalArgumentException("Icon '" + iconName + "' cannot be found in resource folder");
}
File file = null;
try {
file = Paths.get(res.toURI()).toFile();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Icon '" + iconName + "' cannot be converted to file", e);
}
return file.getAbsolutePath();
}
private String getPathString(URI uri) {
return uri.getPath().substring(1);
}
}

View File

@@ -22,25 +22,32 @@ import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URI;
import java.util.Base64;
import java.util.List;
/**
* Responsible to manage the tray icon on macOS and Windows using AWT.
* For Linux, we use {@link AppindicatorTrayMenuController}
*/
@CheckAvailability
@Priority(Priority.FALLBACK)
public class AwtTrayMenuController implements TrayMenuController {
private static final Logger LOG = LoggerFactory.getLogger(AwtTrayMenuController.class);
private static final String DATA_URI_SCHEME = "data:image/png;base64,";
private final PopupMenu menu = new PopupMenu();
private TrayIcon trayIcon;
@CheckAvailability
public static boolean isAvailable() {
return SystemTray.isSupported();
return !SystemUtils.IS_OS_LINUX && SystemTray.isSupported();
}
@Override
public void showTrayIcon(byte[] imageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
var image = Toolkit.getDefaultToolkit().createImage(imageData);
public void showTrayIcon(URI uri, Runnable defaultAction, String tooltip) throws TrayMenuException {
var image = Toolkit.getDefaultToolkit().createImage(getImageBytes(uri));
trayIcon = new TrayIcon(image, tooltip, menu);
trayIcon.setImageAutoSize(true);
@@ -57,11 +64,8 @@ public class AwtTrayMenuController implements TrayMenuController {
}
@Override
public void updateTrayIcon(byte[] imageData) {
if (trayIcon == null) {
throw new IllegalStateException("Failed to update the icon as it has not yet been added");
}
var image = Toolkit.getDefaultToolkit().createImage(imageData);
public void updateTrayIcon(URI uri) {
var image = Toolkit.getDefaultToolkit().createImage(getImageBytes(uri));
trayIcon.setImage(image);
}
@@ -100,4 +104,8 @@ public class AwtTrayMenuController implements TrayMenuController {
}
}
private byte[] getImageBytes(URI uri) {
var data = uri.toString().split(DATA_URI_SCHEME)[1];
return Base64.getDecoder().decode(data);
}
}

View File

@@ -23,7 +23,9 @@ import javafx.beans.Observable;
import javafx.collections.ObservableList;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
@@ -36,6 +38,10 @@ public class TrayMenuBuilder {
private static final String TRAY_ICON_UNLOCKED_MAC = "/img/tray_icon_unlocked_mac@2x.png";
private static final String TRAY_ICON = "/img/tray_icon.png";
private static final String TRAY_ICON_UNLOCKED = "/img/tray_icon_unlocked.png";
private static final String TRAY_ICON_SVG = "tray_icon.svg";
private static final String TRAY_ICON_UNLOCKED_SVG = "tray_icon_unlocked.svg";
private static final String DATA_URI_SCHEME = "data:image/png;base64,";
private static final String FILE_URI_SCHEME = "file:///";
private final ResourceBundle resourceBundle;
private final VaultService vaultService;
@@ -155,10 +161,16 @@ public class TrayMenuBuilder {
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
private byte[] getAppropriateTrayIconImage() {
private URI getAppropriateTrayIconImage() {
boolean isAnyVaultUnlocked = vaults.stream().anyMatch(Vault::isUnlocked);
String resourceName;
if (SystemUtils.IS_OS_LINUX) {
resourceName = isAnyVaultUnlocked ? TRAY_ICON_UNLOCKED_SVG : TRAY_ICON_SVG;
return URI.create(FILE_URI_SCHEME + resourceName);
}
if (SystemUtils.IS_OS_MAC_OSX) {
resourceName = isAnyVaultUnlocked ? TRAY_ICON_UNLOCKED_MAC : TRAY_ICON_MAC;
} else {
@@ -167,10 +179,11 @@ public class TrayMenuBuilder {
try (var image = getClass().getResourceAsStream(resourceName)) {
assert image != null;
return image.readAllBytes();
var imageBytes = image.readAllBytes();
var data = Base64.getEncoder().encodeToString(imageBytes);
return URI.create(DATA_URI_SCHEME + data);
} catch (IOException e) {
throw new UncheckedIOException("Failed to load tray icon image: " + resourceName, e);
}
}
}

View File

@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m32.66 29.319a1.432 1.432 0 0 0 -.66-.319h-1.5a8.125 8.125 0 0 0 1.5-5 11.027 11.027 0 0 0 -.5-3 2.519 2.519 0 0 1 0-1.5 12.987 12.987 0 0 0 .5-3.5c-6.681-1.955-11 0-11 0s-4.313-1.936-11 0a13.272 13.272 0 0 0 .5 3.5 2.519 2.519 0 0 1 0 1.5 11.1 11.1 0 0 0 -.5 3 8.035 8.035 0 0 0 1.5 5h-1.5a1.868 1.868 0 0 0 -.783.319 3.477 3.477 0 0 0 -1.636 2.157 7.058 7.058 0 0 0 -.234 3.18c.307 2.008 1.329 3.344 2.653 3.344h4a.379.379 0 0 0 .277-.187l1.793-1.483a3.263 3.263 0 0 0 .729-2.074 7.857 7.857 0 0 0 .027-.878 23.166 23.166 0 0 0 4.174 1.622 24.4 24.4 0 0 0 4.051-1.614 7.848 7.848 0 0 0 .027.869 3.263 3.263 0 0 0 .729 2.074l1.793 1.484a.61.61 0 0 0 .4.187h4c1.324 0 2.223-1.336 2.529-3.343a7.057 7.057 0 0 0 -.234-3.18 3.477 3.477 0 0 0 -1.635-2.158zm-17.069 6.5c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.048c.051 1.03-.161 2.644-.509 2.834zm6.409-6.819h-2l.5-5a2 2 0 1 1 1 0zm6.38 7.921a.418.418 0 0 1 -.627.111c-.522-.433-1.439-1.2-1.458-1.208-.348-.189-.56-1.8-.505-2.828a15.84 15.84 0 0 0 2.9-2.037 9.322 9.322 0 0 1 -.31 5.962zm-20.924-11.162.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm33.217 1.2a3.021 3.021 0 0 0 -2.658-1.525 1.574 1.574 0 0 0 -.107-1.283l-.745-1.367a1.779 1.779 0 0 0 -1.317-.891l-.7-1.278a2.409 2.409 0 0 0 .229-2.661 2.283 2.283 0 0 0 -2.375-1.454 7.039 7.039 0 0 1 0 3 3.272 3.272 0 0 0 0 1.5c.047.152-.047.3 0 .5.227.04-.069.156.165.14l.653 1.2a1.579 1.579 0 0 0 -.019 1.557l.745 1.367a1.753 1.753 0 0 0 1.045.832 2.66 2.66 0 0 0 -.238 2.916 2.989 2.989 0 0 0 2.326 1.509.79.79 0 0 0 .082 0 .707.707 0 0 0 .717-.6.688.688 0 0 0 -.636-.749 1.546 1.546 0 0 1 -1.2-.78 1.408 1.408 0 0 1 .672-1.95 1.628 1.628 0 0 1 1.179-.089 1.512 1.512 0 0 1 .9.719 1.37 1.37 0 0 1 -.023 1.361.656.656 0 0 0 .284.92.748.748 0 0 0 .981-.266 2.648 2.648 0 0 0 .04-2.633zm-19.673-11.959a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2z" fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1"/></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1"><path d="m15.591 35.824c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.043c.051 1.03-.161 2.644-.509 2.834zm6.409-6.824h-2l.5-5a2 2 0 1 1 1 0zm-14.544-3.241.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm13.544-10.759a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm14.473 6a8.067 8.067 0 0 0 -8.08 8v2.141a3.891 3.891 0 0 0 -2.893 3.734v5.125a23.166 23.166 0 0 1 -4.174-1.623 7.857 7.857 0 0 1 -.027.878 3.263 3.263 0 0 1 -.729 2.074l-1.794 1.483a.379.379 0 0 1 -.276.188h-4c-1.324 0-2.346-1.336-2.653-3.343a7.058 7.058 0 0 1 .234-3.18 3.477 3.477 0 0 1 1.636-2.157 1.868 1.868 0 0 1 .783-.32h1.5a8.035 8.035 0 0 1 -1.5-5 11.1 11.1 0 0 1 .5-3 2.519 2.519 0 0 0 0-1.5 13.272 13.272 0 0 1 -.5-3.5c6.687-1.936 11 0 11 0s4.319-1.955 11 0"/><path d="m39 28h-10v-4a3.13 3.13 0 0 1 3-3 3.087 3.087 0 0 1 3 3v1a1.034 1.034 0 0 0 1 1h1a1.034 1.034 0 0 0 1-1v-1a6 6 0 0 0 -12 0v4h-1a2.073 2.073 0 0 0 -2 2v6a2.073 2.073 0 0 0 2 2h14a2.073 2.073 0 0 0 2-2v-6a2.073 2.073 0 0 0 -2-2zm-5.391 5.94a1.609 1.609 0 0 1 -3.217 0v-1.876a1.609 1.609 0 0 1 3.217 0z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB