javafxports / openjdk-jfx

The openjfx repo has moved to:
https://github.com/openjdk/jfx
GNU General Public License v2.0
1.01k stars 145 forks source link

JDK-8092115: Add JavaFX support for SystemTray #354

Open goxr3plus opened 5 years ago

goxr3plus commented 5 years ago

Long ago i was using Swing which was providing SystemTray https://docs.oracle.com/javase/tutorial/uiswing/misc/systemtray.html

There is nothing like that in JavaFX as i have seen 6 years now .

Can we implement it ?

xqdd commented 5 years ago

Third party version, see https://git.dorkbox.com/dorkbox/SystemTray

pitrsen commented 5 years ago

Question was : if JavaFX doesn't support it...

I found just this : https://bugs.openjdk.java.net/browse/JDK-8092115 It seems like development about SystemTray in JavaFX is stopped.

Anyone some good new about this ? :) Thanks !!!

xqdd commented 5 years ago

In my opinion, if the third party version is good enough for use, it should not waste the precious open source community labor on it ( By the way, there should be a Gitter or Discord channel to discuss something like this)

goxr3plus commented 5 years ago

@XQDD It is not JavaFX implementation though ... it's using Swing/AWT.

brcolow commented 5 years ago

@goxr3plus All that's required is for someone to step up and start the process of adding this functionality to OpenJFX. A new API is possible to add but it takes a commitment of work and time by a volunteer. In other words, just posting an issue on this Github repo will almost surely not make it so an Oracle dev implements this feature - we need the communities support!

goxr3plus commented 5 years ago

@brcolow Yes i understand, it's just i have no clue how to start implementing this :'(

brcolow commented 5 years ago

How to start:

A public API needs to be defined. Most likely it would be a SystemTray class that has various methods for setting menu items, icons, separators, etc. The public API would need to be platform-agnostic but encompass enough of the shared feature set between platforms.

Once the public API is defined you would create a SystemTrayPeer class in the private API that defines the native methods.

Then you would implement the native methods for Windows, macOS, and GTK (Linux) to create the native system trays for each platform.

DJViking commented 5 years ago

Would definitely like to see a JavaFX SystemTray. For Linux it needs implementation at least for both KDE and GTK. The two dominant desktop environments.

A public API could be created for JavaFX. The implementations could be made as third party dependencies for KDE, GNOME, and all the others in Linux, Windows and Mac.

tresf commented 5 years ago

I noticed the dorkbox library was mentioned and wanted to share my experiences. The lack of a reliable library on all Linux desktops makes it impossible to support in a product. I've done a workaround -- inspired in part by Steam -- to just not use the tray area, but instead draw a menu near the dock. Quoting an Ubuntu bug report on the dorkbox repo:

@dorkbox FYI, I've found a reasonable way to draw the menu at the task bar using a minimize/restore listener on a 1px x 1px window, wanted to share. (Pure Java, no JNI). ezgif-2-f6c1a3068cac

I'd be happy to share my technique for drawing the menu at the mouse location. It works pretty well with Gnome, Pantheon and KDE by drawing a menu at the mouse. The code is currently Swing-based (not JavaFX) but a similar technique should be possible. If someone finds this useful and needs to use the code under a different license, let me know.

Sorry if this is off-topic.

goxr3plus commented 5 years ago

Please share it with us, i will try to implement in JavaFx

@tresf

tresf commented 5 years ago

@goxr3plus sure. Apologies as the example's for Swing, hopefully it's of use...

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;

public class TaskTray extends JFrame implements WindowListener {
    private JPopupMenu menu;
    private ActionListener exitListener;

    public TaskTray(String title, JPopupMenu menu, Image image, ActionListener exitListener) {
        this.menu = menu;
        this.exitListener = exitListener;
        this.addListeners();
        this.setUndecorated(true);
        this.setTitle(title);

        this.setSize(0,0);
        this.getContentPane().setBackground(Color.BLACK);

        this.setIconImage(image);
        this.setResizable(false);
        this.addWindowListener(this);
        this.pack();
        this.setVisible(true);
    }

    private void addListeners() {
        this.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                if (exitListener != null) {
                    exitListener.actionPerformed(new ActionEvent(e.getComponent(), e.getID(), "Exit"));
                }
            }
        });
        menu.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                TaskTray.this.setState(JFrame.ICONIFIED);
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
                setState(JFrame.ICONIFIED);
            }
        });
        menu.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {}

            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    menu.setVisible(false);
                }
            }

            @Override
            public void keyReleased(KeyEvent e) {}
        });
    }

    @Override
    public void setTitle(String title) {
        super.setTitle(title);
        TaskTray.setTaskBarTitle(title);
    }

    @Override
    public void windowOpened(WindowEvent windowEvent) {}

    @Override
    public void windowClosing(WindowEvent windowEvent) {}

    @Override
    public void windowClosed(WindowEvent windowEvent) {}

    @Override
    public void windowIconified(WindowEvent windowEvent) {}

    @Override
    public void windowActivated(WindowEvent windowEvent) {}

    @Override
    public void windowDeactivated(WindowEvent windowEvent) {
        menu.setVisible(false);
        this.setState(JFrame.ICONIFIED);
    }

    @Override
    public void windowDeiconified(WindowEvent e) {
        Point p = MouseInfo.getPointerInfo().getLocation();
        this.setLocation(p);
        // call show against parent to prevent un-clickable state
        this.menu.show(this, 0,0);

        // move to mouse cursor; adjusting for screen boundaries
        Point before = this.menu.getLocationOnScreen();
        Point after = new Point();
        after.setLocation(before.x < p.x ? p.x - this.menu.getWidth() : p.x, before.y < p.y ? p.y - this.menu.getHeight() : p.y);
        this.menu.setLocation(after);
    }

    // Fixes Linux taskbar title per http://hg.netbeans.org/core-main/rev/5832261b8434
    public static void setTaskBarTitle(String title) {
        try {
            Class<?> toolkit = Toolkit.getDefaultToolkit().getClass();
            if (toolkit.getName().equals("sun.awt.X11.XToolkit")) {
                final Field awtAppClassName = toolkit.getDeclaredField("awtAppClassName");
                awtAppClassName.setAccessible(true);
                awtAppClassName.set(null, title);
            }
        } catch(Exception ignore) {}
    }

    public static void main(String ... args) throws InterruptedException, InvocationTargetException {
        SwingUtilities.invokeAndWait(() -> {
            // Build a menu
            JMenuItem hello = new JMenuItem("Hello");
            hello.addActionListener(a -> {
                JOptionPane.showMessageDialog(null, "You opened a new window!");
            });
            JMenuItem exit = new JMenuItem("Exit");
            exit.addActionListener(a -> System.exit(0));
            JPopupMenu menu = new JPopupMenu();
            menu.add(hello);
            menu.add(exit);

            // Handle someone closing it manually, set to exit.getActionListeners()[0] if needed
            ActionListener windowClosing = e -> System.out.println("I detected that you exited!");

            // Title
            String title = "MENU";

            // Get an icon from the filesystem
            Image image = null;
            try {
                image = ImageIO.read(new URL("https://via.placeholder.com/40/878f99/FFFFFF/?text=" + title));
            } catch(Exception e) {
                e.printStackTrace();
            }

            // Create a taskbar item that can draw a menu
            new TaskTray(title, menu, image, windowClosing);
        });
    }

}
goxr3plus commented 5 years ago

@tresf Thank you :) 🥇

I am thinking how to make a solution for Windows and Mac now .

tymur-berezhnoi commented 5 years ago

Any updates?