All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.systemTray.ui.osx._OsxNativeTray Maven / Gradle / Ivy

Go to download

Cross-platform SystemTray support for Swing/AWT, GtkStatusIcon, and AppIndicator on Java 6+

There is a newer version: 4.4
Show newest version
/*
 * 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;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy