Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add taskbar menu for trayless OS #411

Merged
merged 10 commits into from Feb 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 12 additions & 4 deletions ant/linux/linux-installer.sh.in
Expand Up @@ -121,6 +121,7 @@ echo "[Desktop Entry]
Type=Application
Name=${project.name}
Exec=java ${launch.opts} -jar \"${jarfile}\"
StartupWMClass=${project.name}
Path=${destdir}
Icon=${destdir}/${linux.icon}
MimeType=application/x-qz;x-scheme-handler/qz;
Expand Down Expand Up @@ -154,7 +155,14 @@ function restart_it() {
# Provide user environmental variables to the sudo environment
function sudo_env() {
userid="$(logname 2>/dev/null || echo $SUDO_USER)"
pid=$(ps aux |grep "^$userid" |grep "dbus-daemon" | grep -- "--config-file=" |awk '{print $2}')
if [ -z "$1" ]; then
daemon="dbus-daemon"
lookfor="--config-file="
else
daemon="$1"
lookfor="$2"
fi
pid=$(ps aux |grep "^$userid" |grep "$daemon" | grep -- "$lookfor" |awk '{print $2}')
# Replace null delimiters with newline for grep
envt=$(cat "/proc/$pid/environ" 2> /dev/null |tr '\0' '\n')

Expand All @@ -171,9 +179,8 @@ function sudo_env() {
elif initctl --user get-env $i > /dev/null 2>&1; then
eval "$i=$(initctl --user get-env $i)" > /dev/null 2>&1
export $i > /dev/null 2>&1
fi

echo -e " $i=${!i}"
fi
# echo -e " $i=${!i}"
done

# Handle Ubuntu Gnome
Expand Down Expand Up @@ -238,6 +245,7 @@ if [ $? -ne 0 ]; then
else
# Start ${project.name} as the user that's logged in
sudo_env
sudo_env "ibus-daemon" "--panel"
userid="$(logname 2>/dev/null || echo $SUDO_USER)"
sudo -E -u $userid nohup "java" ${launch.opts} -jar "${jarfile}" > /dev/null 2>&1 &
progress_dialog 100 "Finished. ${project.name} should start automatically."
Expand Down
25 changes: 15 additions & 10 deletions src/qz/common/TrayManager.java
Expand Up @@ -13,16 +13,14 @@
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.jdesktop.swinghelper.tray.JXTrayIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qz.auth.Certificate;
import qz.deploy.DeployUtilities;
import qz.deploy.LinuxCertificate;
import qz.deploy.WindowsDeploy;
import qz.ui.*;
import qz.ui.tray.ClassicTrayIcon;
import qz.ui.tray.ModernTrayIcon;
import qz.ui.tray.TrayType;
import qz.utils.*;
import qz.ws.PrintSocketServer;
import qz.ws.SingleInstanceChecker;
Expand All @@ -49,7 +47,7 @@ public class TrayManager {
private final IconCache iconCache;

// Custom swing pop-up menu
private JXTrayIcon tray;
private TrayType tray;

private ConfirmDialog confirmDialog;
private GatewayDialog gatewayDialog;
Expand Down Expand Up @@ -87,13 +85,12 @@ public TrayManager() {
SystemUtilities.setSystemLookAndFeel();

if (SystemTray.isSupported()) {
Image blank = new ImageIcon(new byte[1]).getImage();
if (SystemUtilities.isWindows()) {
tray = new JXTrayIcon(blank);
tray = TrayType.JX.init();
} else if (SystemUtilities.isMac()) {
tray = new ClassicTrayIcon(blank);
tray = TrayType.CLASSIC.init();
} else {
tray = new ModernTrayIcon(blank);
tray = TrayType.MODERN.init();
}

// Iterates over all images denoted by IconCache.getTypes() and caches them
Expand All @@ -102,18 +99,26 @@ public TrayManager() {
tray.setToolTip(name);

try {
SystemTray.getSystemTray().add(tray);
SystemTray.getSystemTray().add(tray.tray());
} catch (AWTException awt) {
log.error("Could not attach tray", awt);
}
} else if (!GraphicsEnvironment.isHeadless()) {
iconCache = new IconCache();
tray = TrayType.TASKBAR.init(exitListener);
tray.setImage(iconCache.getImage(IconCache.Icon.DANGER_ICON, tray.getSize()));
tray.setToolTip(name);
tray.showTaskbar();
} else {
iconCache = new IconCache();
}

// Linux specific tasks
if (SystemUtilities.isLinux()) {
// Fix the tray icon to look proper on Ubuntu
UbuntuUtilities.fixTrayIcons(iconCache);
if (SystemTray.isSupported()) {
UbuntuUtilities.fixTrayIcons(iconCache);
}
// Install cert into user's nssdb for Chrome, etc
LinuxCertificate.installCertificate();
} else if (SystemUtilities.isWindows()) {
Expand Down
134 changes: 134 additions & 0 deletions src/qz/ui/tray/TaskbarTrayIcon.java
@@ -0,0 +1,134 @@
package qz.ui.tray;

import qz.common.Constants;
import qz.utils.SystemUtilities;
import qz.utils.UbuntuUtilities;

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;

public class TaskbarTrayIcon extends JFrame implements WindowListener {
private Dimension iconSize;
private JPopupMenu popup;

public TaskbarTrayIcon(Image trayImage, final ActionListener exitListener) {
super(Constants.ABOUT_TITLE);
initializeComponents(trayImage, exitListener);
}

private void initializeComponents(Image trayImage, final ActionListener exitListener) {
// must come first
setUndecorated(true);
setTaskBarTitle(getTitle());
setSize(0,0);
getContentPane().setBackground(Color.BLACK);
if (SystemUtilities.isUbuntu()) {
// attempt to camouflage the single pixel left behind
getContentPane().setBackground(UbuntuUtilities.getTrayColor());
}

iconSize = new Dimension(40, 40);

setIconImage(trayImage);
setResizable(false);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
exitListener.actionPerformed(new ActionEvent(e.getComponent(), e.getID(), "Exit"));
}
});
addWindowListener(this);
}

// 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) {}
}

/**
* Returns the "tray" icon size (not the dialog size)
*/
@Override
public Dimension getSize() {
return iconSize;
}

public void setJPopupMenu(final JPopupMenu popup) {
this.popup = popup;
this.popup.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent popupMenuEvent) {}

@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent popupMenuEvent) {
setState(JFrame.ICONIFIED);
}

@Override
public void popupMenuCanceled(PopupMenuEvent popupMenuEvent) {
setState(JFrame.ICONIFIED);
}
});
this.popup.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent keyEvent) {}

@Override
public void keyPressed(KeyEvent keyEvent) {
if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) {
TaskbarTrayIcon.this.popup.setVisible(false);
}
}

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

public void displayMessage(String caption, String text, TrayIcon.MessageType level) { /* noop */ }

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

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

@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) {
popup.setVisible(false);
setState(JFrame.ICONIFIED);
}
}
90 changes: 90 additions & 0 deletions src/qz/ui/tray/TrayType.java
@@ -0,0 +1,90 @@
package qz.ui.tray;

import org.jdesktop.swinghelper.tray.JXTrayIcon;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;

/**
* Wrapper class to allow popup menu on a tray-less OS
* @author Tres Finocchiaro
*/
public enum TrayType {
JX,
CLASSIC,
MODERN,
TASKBAR;

private JXTrayIcon tray = null;
private TaskbarTrayIcon taskbar = null;

public JXTrayIcon tray() { return tray; }

public TrayType init() {
return init(null);
}

public TrayType init(ActionListener exitListener) {
switch (this) {
case JX:
tray = new JXTrayIcon(blankImage());
case CLASSIC:
tray = new ClassicTrayIcon(blankImage());
case MODERN:
tray = new ModernTrayIcon(blankImage());
default:
taskbar = new TaskbarTrayIcon(blankImage(), exitListener);
}
return this;
}

private static Image blankImage() {
return new ImageIcon(new byte[1]).getImage();
}

public boolean isTray() { return tray != null; }

public boolean getTaskbar() { return taskbar != null; }

public void setImage(Image image) {
if (isTray()) {
tray.setImage(image);
} else {
taskbar.setIconImage(image);
}
}

public Dimension getSize() {
return isTray() ? tray.getSize() : taskbar.getSize();
}

public void setToolTip(String tooltip) {
if (isTray()) {
tray.setToolTip(tooltip);
}
}

public void setJPopupMenu(JPopupMenu popup) {
if (isTray()) {
tray.setJPopupMenu(popup);
} else {
taskbar.setJPopupMenu(popup);
}
}

public void displayMessage(String caption, String text, TrayIcon.MessageType level) {
if (isTray()) {
tray.displayMessage(caption, text, level);
} else {
taskbar.displayMessage(caption, text, level);
}
}

public void showTaskbar() {
if (getTaskbar()) {
taskbar.setVisible(true);
taskbar.setState(Frame.ICONIFIED);
}
}
}