| /* | |
| * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package java.awt; | |
| import java.awt.event.ActionListener; | |
| import java.awt.peer.SystemTrayPeer; | |
| import java.beans.PropertyChangeListener; | |
| import java.beans.PropertyChangeSupport; | |
| import java.util.Vector; | |
| import sun.awt.AWTAccessor; | |
| import sun.awt.AppContext; | |
| import sun.awt.HeadlessToolkit; | |
| import sun.awt.SunToolkit; | |
| /** | |
| * The {@code SystemTray} class represents the system tray for a | |
| * desktop. On Microsoft Windows it is referred to as the "Taskbar | |
| * Status Area", on Gnome it is referred to as the "Notification | |
| * Area", on KDE it is referred to as the "System Tray". The system | |
| * tray is shared by all applications running on the desktop. | |
| * | |
| * <p> On some platforms the system tray may not be present or may not | |
| * be supported, in this case {@link SystemTray#getSystemTray()} | |
| * throws {@link UnsupportedOperationException}. To detect whether the | |
| * system tray is supported, use {@link SystemTray#isSupported}. | |
| * | |
| * <p>The {@code SystemTray} may contain one or more {@link | |
| * TrayIcon TrayIcons}, which are added to the tray using the {@link | |
| * #add} method, and removed when no longer needed, using the | |
| * {@link #remove}. {@code TrayIcon} consists of an | |
| * image, a popup menu and a set of associated listeners. Please see | |
| * the {@link TrayIcon} class for details. | |
| * | |
| * <p>Every Java application has a single {@code SystemTray} | |
| * instance that allows the app to interface with the system tray of | |
| * the desktop while the app is running. The {@code SystemTray} | |
| * instance can be obtained from the {@link #getSystemTray} method. | |
| * An application may not create its own instance of | |
| * {@code SystemTray}. | |
| * | |
| * <p>The following code snippet demonstrates how to access | |
| * and customize the system tray: | |
| * <pre> | |
| * <code> | |
| * {@link TrayIcon} trayIcon = null; | |
| * if (SystemTray.isSupported()) { | |
| * // get the SystemTray instance | |
| * SystemTray tray = SystemTray.{@link #getSystemTray}; | |
| * // load an image | |
| * {@link java.awt.Image} image = {@link java.awt.Toolkit#getImage(String) Toolkit.getDefaultToolkit().getImage}(...); | |
| * // create a action listener to listen for default action executed on the tray icon | |
| * {@link java.awt.event.ActionListener} listener = new {@link java.awt.event.ActionListener ActionListener}() { | |
| * public void {@link java.awt.event.ActionListener#actionPerformed actionPerformed}({@link java.awt.event.ActionEvent} e) { | |
| * // execute default action of the application | |
| * // ... | |
| * } | |
| * }; | |
| * // create a popup menu | |
| * {@link java.awt.PopupMenu} popup = new {@link java.awt.PopupMenu#PopupMenu PopupMenu}(); | |
| * // create menu item for the default action | |
| * MenuItem defaultItem = new MenuItem(...); | |
| * defaultItem.addActionListener(listener); | |
| * popup.add(defaultItem); | |
| * /// ... add other items | |
| * // construct a TrayIcon | |
| * trayIcon = new {@link TrayIcon#TrayIcon(java.awt.Image, String, java.awt.PopupMenu) TrayIcon}(image, "Tray Demo", popup); | |
| * // set the TrayIcon properties | |
| * trayIcon.{@link TrayIcon#addActionListener(java.awt.event.ActionListener) addActionListener}(listener); | |
| * // ... | |
| * // add the tray image | |
| * try { | |
| * tray.{@link SystemTray#add(TrayIcon) add}(trayIcon); | |
| * } catch (AWTException e) { | |
| * System.err.println(e); | |
| * } | |
| * // ... | |
| * } else { | |
| * // disable tray option in your application or | |
| * // perform other actions | |
| * ... | |
| * } | |
| * // ... | |
| * // some time later | |
| * // the application state has changed - update the image | |
| * if (trayIcon != null) { | |
| * trayIcon.{@link TrayIcon#setImage(java.awt.Image) setImage}(updatedImage); | |
| * } | |
| * // ... | |
| * </code> | |
| * </pre> | |
| * | |
| * @since 1.6 | |
| * @see TrayIcon | |
| * | |
| * @author Bino George | |
| * @author Denis Mikhalkin | |
| * @author Sharon Zakhour | |
| * @author Anton Tarasov | |
| */ | |
| public class SystemTray { | |
| private static SystemTray systemTray; | |
| private int currentIconID = 0; // each TrayIcon added gets a unique ID | |
| private transient SystemTrayPeer peer; | |
| private static final TrayIcon[] EMPTY_TRAY_ARRAY = new TrayIcon[0]; | |
| static { | |
| AWTAccessor.setSystemTrayAccessor(SystemTray::firePropertyChange); | |
| } | |
| /** | |
| * Private {@code SystemTray} constructor. | |
| * | |
| */ | |
| private SystemTray() { | |
| addNotify(); | |
| } | |
| /** | |
| * Gets the {@code SystemTray} instance that represents the | |
| * desktop's tray area. This always returns the same instance per | |
| * application. On some platforms the system tray may not be | |
| * supported. You may use the {@link #isSupported} method to | |
| * check if the system tray is supported. | |
| * | |
| * @return the {@code SystemTray} instance that represents | |
| * the desktop's tray area | |
| * @throws UnsupportedOperationException if the system tray isn't | |
| * supported by the current platform | |
| * @throws HeadlessException if | |
| * {@code GraphicsEnvironment.isHeadless()} returns {@code true} | |
| * @see #add(TrayIcon) | |
| * @see TrayIcon | |
| * @see #isSupported | |
| */ | |
| public static SystemTray getSystemTray() { | |
| if (GraphicsEnvironment.isHeadless()) { | |
| throw new HeadlessException(); | |
| } | |
| initializeSystemTrayIfNeeded(); | |
| if (!isSupported()) { | |
| throw new UnsupportedOperationException( | |
| "The system tray is not supported on the current platform."); | |
| } | |
| return systemTray; | |
| } | |
| /** | |
| * Returns whether the system tray is supported on the current | |
| * platform. In addition to displaying the tray icon, minimal | |
| * system tray support includes either a popup menu (see {@link | |
| * TrayIcon#setPopupMenu(PopupMenu)}) or an action event (see | |
| * {@link TrayIcon#addActionListener(ActionListener)}). | |
| * | |
| * <p>Developers should not assume that all of the system tray | |
| * functionality is supported. To guarantee that the tray icon's | |
| * default action is always accessible, add the default action to | |
| * both the action listener and the popup menu. See the {@link | |
| * SystemTray example} for an example of how to do this. | |
| * | |
| * <p><b>Note</b>: When implementing {@code SystemTray} and | |
| * {@code TrayIcon} it is <em>strongly recommended</em> that | |
| * you assign different gestures to the popup menu and an action | |
| * event. Overloading a gesture for both purposes is confusing | |
| * and may prevent the user from accessing one or the other. | |
| * | |
| * @see #getSystemTray | |
| * @return {@code false} if no system tray access is supported; this | |
| * method returns {@code true} if the minimal system tray access is | |
| * supported but does not guarantee that all system tray | |
| * functionality is supported for the current platform | |
| */ | |
| public static boolean isSupported() { | |
| Toolkit toolkit = Toolkit.getDefaultToolkit(); | |
| if (toolkit instanceof SunToolkit) { | |
| // connecting tray to native resource | |
| initializeSystemTrayIfNeeded(); | |
| return ((SunToolkit)toolkit).isTraySupported(); | |
| } else if (toolkit instanceof HeadlessToolkit) { | |
| // skip initialization as the init routine | |
| // throws HeadlessException | |
| return ((HeadlessToolkit)toolkit).isTraySupported(); | |
| } else { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Adds a {@code TrayIcon} to the {@code SystemTray}. | |
| * The tray icon becomes visible in the system tray once it is | |
| * added. The order in which icons are displayed in a tray is not | |
| * specified - it is platform and implementation-dependent. | |
| * | |
| * <p> All icons added by the application are automatically | |
| * removed from the {@code SystemTray} upon application exit | |
| * and also when the desktop system tray becomes unavailable. | |
| * | |
| * @param trayIcon the {@code TrayIcon} to be added | |
| * @throws NullPointerException if {@code trayIcon} is | |
| * {@code null} | |
| * @throws IllegalArgumentException if the same instance of | |
| * a {@code TrayIcon} is added more than once | |
| * @throws AWTException if the desktop system tray is missing | |
| * @see #remove(TrayIcon) | |
| * @see #getSystemTray | |
| * @see TrayIcon | |
| * @see java.awt.Image | |
| */ | |
| public void add(TrayIcon trayIcon) throws AWTException { | |
| if (trayIcon == null) { | |
| throw new NullPointerException("adding null TrayIcon"); | |
| } | |
| TrayIcon[] oldArray; | |
| TrayIcon[] newArray; | |
| Vector<TrayIcon> icons; | |
| synchronized (this) { | |
| oldArray = systemTray.getTrayIcons(); | |
| @SuppressWarnings("unchecked") | |
| Vector<TrayIcon> tmp = (Vector<TrayIcon>)AppContext.getAppContext().get(TrayIcon.class); | |
| icons = tmp; | |
| if (icons == null) { | |
| icons = new Vector<>(3); | |
| AppContext.getAppContext().put(TrayIcon.class, icons); | |
| } else if (icons.contains(trayIcon)) { | |
| throw new IllegalArgumentException("adding TrayIcon that is already added"); | |
| } | |
| icons.add(trayIcon); | |
| newArray = systemTray.getTrayIcons(); | |
| trayIcon.setID(++currentIconID); | |
| } | |
| try { | |
| trayIcon.addNotify(); | |
| } catch (AWTException e) { | |
| icons.remove(trayIcon); | |
| throw e; | |
| } | |
| firePropertyChange("trayIcons", oldArray, newArray); | |
| } | |
| /** | |
| * Removes the specified {@code TrayIcon} from the | |
| * {@code SystemTray}. | |
| * | |
| * <p> All icons added by the application are automatically | |
| * removed from the {@code SystemTray} upon application exit | |
| * and also when the desktop system tray becomes unavailable. | |
| * | |
| * <p> If {@code trayIcon} is {@code null} or was not | |
| * added to the system tray, no exception is thrown and no action | |
| * is performed. | |
| * | |
| * @param trayIcon the {@code TrayIcon} to be removed | |
| * @see #add(TrayIcon) | |
| * @see TrayIcon | |
| */ | |
| public void remove(TrayIcon trayIcon) { | |
| if (trayIcon == null) { | |
| return; | |
| } | |
| TrayIcon[] oldArray; | |
| TrayIcon[] newArray; | |
| synchronized (this) { | |
| oldArray = systemTray.getTrayIcons(); | |
| @SuppressWarnings("unchecked") | |
| Vector<TrayIcon> icons = (Vector<TrayIcon>)AppContext.getAppContext().get(TrayIcon.class); | |
| // TrayIcon with no peer is not contained in the array. | |
| if (icons == null || !icons.remove(trayIcon)) { | |
| return; | |
| } | |
| trayIcon.removeNotify(); | |
| newArray = systemTray.getTrayIcons(); | |
| } | |
| firePropertyChange("trayIcons", oldArray, newArray); | |
| } | |
| /** | |
| * Returns an array of all icons added to the tray by this | |
| * application. You can't access the icons added by another | |
| * application. Some browsers partition applets in different | |
| * code bases into separate contexts, and establish walls between | |
| * these contexts. In such a scenario, only the tray icons added | |
| * from this context will be returned. | |
| * | |
| * <p> The returned array is a copy of the actual array and may be | |
| * modified in any way without affecting the system tray. To | |
| * remove a {@code TrayIcon} from the | |
| * {@code SystemTray}, use the {@link | |
| * #remove(TrayIcon)} method. | |
| * | |
| * @return an array of all tray icons added to this tray, or an | |
| * empty array if none has been added | |
| * @see #add(TrayIcon) | |
| * @see TrayIcon | |
| */ | |
| public TrayIcon[] getTrayIcons() { | |
| @SuppressWarnings("unchecked") | |
| Vector<TrayIcon> icons = (Vector<TrayIcon>)AppContext.getAppContext().get(TrayIcon.class); | |
| if (icons != null) { | |
| return icons.toArray(EMPTY_TRAY_ARRAY); | |
| } | |
| return EMPTY_TRAY_ARRAY; | |
| } | |
| /** | |
| * Returns the size, in pixels, of the space that a tray icon will | |
| * occupy in the system tray. Developers may use this method to | |
| * acquire the preferred size for the tray icon before it is created. | |
| * For convenience, there is a similar method {@link TrayIcon#getSize} | |
| * in the {@code TrayIcon} class. | |
| * | |
| * @return the default size of a tray icon, in pixels | |
| * @see TrayIcon#setImageAutoSize(boolean) | |
| * @see java.awt.Image | |
| * @see TrayIcon#getSize() | |
| */ | |
| public Dimension getTrayIconSize() { | |
| return peer.getTrayIconSize(); | |
| } | |
| /** | |
| * Adds a {@code PropertyChangeListener} to the list of listeners for the | |
| * specific property. The following properties are currently supported: | |
| * | |
| * <table class="striped"> | |
| * <caption>SystemTray properties</caption> | |
| * <thead> | |
| * <tr> | |
| * <th scope="col">Property | |
| * <th scope="col">Description | |
| * </thead> | |
| * <tbody> | |
| * <tr> | |
| * <th scope="row">{@code trayIcons} | |
| * <td>The {@code SystemTray}'s array of {@code TrayIcon} objects. The | |
| * array is accessed via the {@link #getTrayIcons} method. This property | |
| * is changed when a tray icon is added to (or removed from) the system | |
| * tray. For example, this property is changed when the system tray | |
| * becomes unavailable on the desktop and the tray icons are | |
| * automatically removed. | |
| * <tr> | |
| * <th scope="row">{@code systemTray} | |
| * <td>This property contains {@code SystemTray} instance when the | |
| * system tray is available or {@code null} otherwise. This property is | |
| * changed when the system tray becomes available or unavailable on the | |
| * desktop. The property is accessed by the {@link #getSystemTray} | |
| * method. | |
| * </tbody> | |
| * </table> | |
| * <p> | |
| * The {@code listener} listens to property changes only in this context. | |
| * <p> | |
| * If {@code listener} is {@code null}, no exception is thrown | |
| * and no action is performed. | |
| * | |
| * @param propertyName the specified property | |
| * @param listener the property change listener to be added | |
| * | |
| * @see #removePropertyChangeListener | |
| * @see #getPropertyChangeListeners | |
| */ | |
| public synchronized void addPropertyChangeListener(String propertyName, | |
| PropertyChangeListener listener) | |
| { | |
| if (listener == null) { | |
| return; | |
| } | |
| getCurrentChangeSupport().addPropertyChangeListener(propertyName, listener); | |
| } | |
| /** | |
| * Removes a {@code PropertyChangeListener} from the listener list | |
| * for a specific property. | |
| * <p> | |
| * The {@code PropertyChangeListener} must be from this context. | |
| * <p> | |
| * If {@code propertyName} or {@code listener} is {@code null} or invalid, | |
| * no exception is thrown and no action is taken. | |
| * | |
| * @param propertyName the specified property | |
| * @param listener the PropertyChangeListener to be removed | |
| * | |
| * @see #addPropertyChangeListener | |
| * @see #getPropertyChangeListeners | |
| */ | |
| public synchronized void removePropertyChangeListener(String propertyName, | |
| PropertyChangeListener listener) | |
| { | |
| if (listener == null) { | |
| return; | |
| } | |
| getCurrentChangeSupport().removePropertyChangeListener(propertyName, listener); | |
| } | |
| /** | |
| * Returns an array of all the listeners that have been associated | |
| * with the named property. | |
| * <p> | |
| * Only the listeners in this context are returned. | |
| * | |
| * @param propertyName the specified property | |
| * @return all of the {@code PropertyChangeListener}s associated with | |
| * the named property; if no such listeners have been added or | |
| * if {@code propertyName} is {@code null} or invalid, an empty | |
| * array is returned | |
| * | |
| * @see #addPropertyChangeListener | |
| * @see #removePropertyChangeListener | |
| */ | |
| public synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { | |
| return getCurrentChangeSupport().getPropertyChangeListeners(propertyName); | |
| } | |
| // *************************************************************** | |
| // *************************************************************** | |
| /** | |
| * Support for reporting bound property changes for Object properties. | |
| * This method can be called when a bound property has changed and it will | |
| * send the appropriate PropertyChangeEvent to any registered | |
| * PropertyChangeListeners. | |
| * | |
| * @param propertyName the property whose value has changed | |
| * @param oldValue the property's previous value | |
| * @param newValue the property's new value | |
| */ | |
| private void firePropertyChange(String propertyName, | |
| Object oldValue, Object newValue) | |
| { | |
| if (oldValue != null && oldValue.equals(newValue)) { | |
| return; | |
| } | |
| getCurrentChangeSupport().firePropertyChange(propertyName, oldValue, newValue); | |
| } | |
| /** | |
| * Returns the current PropertyChangeSupport instance for the | |
| * calling thread's context. | |
| * | |
| * @return this thread's context's PropertyChangeSupport | |
| */ | |
| private synchronized PropertyChangeSupport getCurrentChangeSupport() { | |
| PropertyChangeSupport changeSupport = | |
| (PropertyChangeSupport)AppContext.getAppContext().get(SystemTray.class); | |
| if (changeSupport == null) { | |
| changeSupport = new PropertyChangeSupport(this); | |
| AppContext.getAppContext().put(SystemTray.class, changeSupport); | |
| } | |
| return changeSupport; | |
| } | |
| synchronized void addNotify() { | |
| if (peer == null) { | |
| Toolkit toolkit = Toolkit.getDefaultToolkit(); | |
| if (toolkit instanceof SunToolkit) { | |
| peer = ((SunToolkit)Toolkit.getDefaultToolkit()).createSystemTray(this); | |
| } else if (toolkit instanceof HeadlessToolkit) { | |
| peer = ((HeadlessToolkit)Toolkit.getDefaultToolkit()).createSystemTray(this); | |
| } | |
| } | |
| } | |
| private static void initializeSystemTrayIfNeeded() { | |
| synchronized (SystemTray.class) { | |
| if (systemTray == null) { | |
| systemTray = new SystemTray(); | |
| } | |
| } | |
| } | |
| } |