/* * Copyright 2021 dorkbox, llc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dorkbox.systemTray.ui.osx; import java.io.File; import dorkbox.jna.macos.cocoa.NSImage; import dorkbox.jna.macos.cocoa.NSStatusBar; import dorkbox.jna.macos.cocoa.NSStatusItem; import dorkbox.jna.macos.cocoa.NSString; import dorkbox.systemTray.MenuItem; import dorkbox.systemTray.Tray; import dorkbox.systemTray.util.ImageResizeUtil; /** * */ public class _OsxNativeTray extends Tray { // is the system tray visible or not. private volatile boolean visible = false; private volatile File imageFile; private volatile String tooltipText = ""; private final Object keepAliveLock = new Object[0]; @SuppressWarnings("FieldCanBeLocal") private final Thread keepAliveThread; // references are ALSO to prevent GC private final NSStatusBar statusBar; private volatile NSStatusItem statusItem; private volatile NSString statusItemTooltip; private volatile NSImage statusItemImage; @SuppressWarnings("unused") public _OsxNativeTray(final String trayName, final ImageResizeUtil imageResizeUtil, final Runnable onRemoveEvent) { super(onRemoveEvent); // THIS WILL NOT keep the app running, so we use a "keep-alive" thread so this behavior is THE SAME across // all platforms. keepAliveThread = new Thread(()->{ synchronized (keepAliveLock) { keepAliveLock.notifyAll(); try { keepAliveLock.wait(); } catch (InterruptedException ignored) { } } }, "TrayKeepAliveThread"); keepAliveThread.start(); // we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization. final OsxMenu osxMenu = new OsxMenu() { @Override public void setEnabled(final MenuItem menuItem) { if (statusItem == null) { statusItem = statusBar.newStatusItem(); statusItem.setHighlightMode(true); statusItem.setMenu(this._nativeMenu); } boolean enabled = menuItem.getEnabled(); if (visible && !enabled) { statusBar.removeStatusItem(statusItem); visible = false; } else if (!visible && enabled) { visible = true; // don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we // want to make sure keep the tooltip text the same as before. statusItem.setToolTip(statusItemTooltip); statusItem.setImage(statusItemImage); } } @Override public void setImage(final MenuItem menuItem) { imageFile = menuItem.getImage(); if (statusItem == null) { statusItem = statusBar.newStatusItem(); statusItem.setHighlightMode(true); statusItem.setMenu(this._nativeMenu); } if (imageFile == null) { statusItemImage = null; } else { statusItemImage = new NSImage(imageFile); } statusItem.setImage(statusItemImage); // don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we // want to make sure keep the tooltip text the same as before. statusItem.setToolTip(statusItemTooltip); } @Override public void setText(final MenuItem menuItem) { // no op. } @Override public void setShortcut(final MenuItem menuItem) { // no op } @Override public void setTooltip(final MenuItem menuItem) { if (statusItem == null) { statusItem = statusBar.newStatusItem(); statusItem.setHighlightMode(true); statusItem.setMenu(this._nativeMenu); } final String text = menuItem.getTooltip(); if (tooltipText != null && tooltipText.equals(text) || tooltipText == null && text != null) { return; } if (text == null) { tooltipText = ""; } else { tooltipText = text; } statusItemTooltip = new NSString(tooltipText); // don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we // want to make sure keep the tooltip text the same as before. statusItem.setImage(statusItemImage); statusItem.setToolTip(statusItemTooltip); } @Override public void remove() { if (statusItem != null) { statusBar.removeStatusItem(statusItem); } statusItem = null; statusItemTooltip = null; statusItemImage = null; // make sure this thread doesn't keep the JVM alive anymore synchronized (keepAliveLock) { keepAliveLock.notifyAll(); } super.remove(); } }; statusBar = NSStatusBar.systemStatusBar(); bind(osxMenu, null, imageResizeUtil); } @Override public boolean hasImage() { return imageFile != null; } }