0N/A/*
3876N/A * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage sun.awt.X11;
0N/A
0N/Aimport java.awt.*;
0N/Aimport java.awt.event.*;
0N/Aimport java.awt.peer.TrayIconPeer;
0N/Aimport sun.awt.*;
0N/Aimport java.awt.image.*;
0N/Aimport java.text.BreakIterator;
0N/Aimport java.util.concurrent.ArrayBlockingQueue;
0N/Aimport java.security.AccessController;
0N/Aimport java.security.PrivilegedAction;
0N/Aimport java.lang.reflect.InvocationTargetException;
1696N/Aimport sun.util.logging.PlatformLogger;
0N/A
1066N/Apublic class XTrayIconPeer implements TrayIconPeer,
1066N/A InfoWindow.Balloon.LiveArguments,
1066N/A InfoWindow.Tooltip.LiveArguments
1066N/A{
1696N/A private static final PlatformLogger ctrLog = PlatformLogger.getLogger("sun.awt.X11.XTrayIconPeer.centering");
0N/A
0N/A TrayIcon target;
0N/A TrayIconEventProxy eventProxy;
0N/A XTrayIconEmbeddedFrame eframe;
0N/A TrayIconCanvas canvas;
1066N/A InfoWindow.Balloon balloon;
1066N/A InfoWindow.Tooltip tooltip;
0N/A PopupMenu popup;
0N/A String tooltipString;
0N/A boolean isTrayIconDisplayed;
0N/A long eframeParentID;
0N/A final XEventDispatcher parentXED, eframeXED;
0N/A
0N/A static final XEventDispatcher dummyXED = new XEventDispatcher() {
0N/A public void dispatchEvent(XEvent ev) {}
0N/A };
0N/A
0N/A volatile boolean isDisposed;
0N/A
0N/A boolean isParentWindowLocated;
0N/A int old_x, old_y;
0N/A int ex_width, ex_height;
0N/A
0N/A final static int TRAY_ICON_WIDTH = 24;
0N/A final static int TRAY_ICON_HEIGHT = 24;
0N/A
0N/A XTrayIconPeer(TrayIcon target)
0N/A throws AWTException
0N/A {
0N/A this.target = target;
0N/A
0N/A eventProxy = new TrayIconEventProxy(this);
0N/A
0N/A canvas = new TrayIconCanvas(target, TRAY_ICON_WIDTH, TRAY_ICON_HEIGHT);
0N/A
0N/A eframe = new XTrayIconEmbeddedFrame();
0N/A
0N/A eframe.setSize(TRAY_ICON_WIDTH, TRAY_ICON_HEIGHT);
0N/A eframe.add(canvas);
0N/A
0N/A // Fix for 6317038: as EmbeddedFrame is instance of Frame, it is blocked
0N/A // by modal dialogs, but in the case of TrayIcon it shouldn't. So we
0N/A // set ModalExclusion property on it.
0N/A AccessController.doPrivileged(new PrivilegedAction() {
0N/A public Object run() {
0N/A eframe.setModalExclusionType(Dialog.ModalExclusionType.TOOLKIT_EXCLUDE);
0N/A return null;
0N/A }
0N/A });
0N/A
0N/A
0N/A if (XWM.getWMID() != XWM.METACITY_WM) {
0N/A parentXED = dummyXED; // We don't like to leave it 'null'.
0N/A
0N/A } else {
0N/A parentXED = new XEventDispatcher() {
0N/A // It's executed under AWTLock.
0N/A public void dispatchEvent(XEvent ev) {
216N/A if (isDisposed() || ev.get_type() != XConstants.ConfigureNotify) {
0N/A return;
0N/A }
0N/A
0N/A XConfigureEvent ce = ev.get_xconfigure();
0N/A
1696N/A ctrLog.fine("ConfigureNotify on parent of {0}: {1}x{2}+{3}+{4} (old: {5}+{6})",
1696N/A XTrayIconPeer.this, ce.get_width(), ce.get_height(),
1696N/A ce.get_x(), ce.get_y(), old_x, old_y);
0N/A
0N/A // A workaround for Gnome/Metacity (it doesn't affect the behaviour on KDE).
0N/A // On Metacity the EmbeddedFrame's parent window bounds are larger
0N/A // than TrayIcon size required (that is we need a square but a rectangle
0N/A // is provided by the Panel Notification Area). The parent's background color
0N/A // differs from the Panel's one. To hide the background we resize parent
0N/A // window so that it fits the EmbeddedFrame.
0N/A // However due to resizing the parent window it loses centering in the Panel.
0N/A // We center it when discovering that some of its side is of size greater
0N/A // than the fixed value. Centering is being done by "X" (when the parent's width
0N/A // is greater) and by "Y" (when the parent's height is greater).
0N/A
0N/A // Actually we need this workaround until we could detect taskbar color.
0N/A
0N/A if (ce.get_height() != TRAY_ICON_HEIGHT && ce.get_width() != TRAY_ICON_WIDTH) {
0N/A
0N/A // If both the height and the width differ from the fixed size then WM
0N/A // must level at least one side to the fixed size. For some reason it may take
0N/A // a few hops (even after reparenting) and we have to skip the intermediate ones.
1696N/A ctrLog.fine("ConfigureNotify on parent of {0}. Skipping as intermediate resizing.",
1696N/A XTrayIconPeer.this);
0N/A return;
0N/A
0N/A } else if (ce.get_height() > TRAY_ICON_HEIGHT) {
0N/A
1696N/A ctrLog.fine("ConfigureNotify on parent of {0}. Centering by \"Y\".",
1696N/A XTrayIconPeer.this);
0N/A
0N/A XlibWrapper.XMoveResizeWindow(XToolkit.getDisplay(), eframeParentID,
0N/A ce.get_x(),
0N/A ce.get_y()+ce.get_height()/2-TRAY_ICON_HEIGHT/2,
0N/A TRAY_ICON_WIDTH,
0N/A TRAY_ICON_HEIGHT);
0N/A ex_height = ce.get_height();
0N/A ex_width = 0;
0N/A
0N/A } else if (ce.get_width() > TRAY_ICON_WIDTH) {
0N/A
1696N/A ctrLog.fine("ConfigureNotify on parent of {0}. Centering by \"X\".",
1696N/A XTrayIconPeer.this);
0N/A
0N/A XlibWrapper.XMoveResizeWindow(XToolkit.getDisplay(), eframeParentID,
0N/A ce.get_x()+ce.get_width()/2 - TRAY_ICON_WIDTH/2,
0N/A ce.get_y(),
0N/A TRAY_ICON_WIDTH,
0N/A TRAY_ICON_HEIGHT);
0N/A ex_width = ce.get_width();
0N/A ex_height = 0;
0N/A
0N/A } else if (isParentWindowLocated && ce.get_x() != old_x && ce.get_y() != old_y) {
0N/A // If moving by both "X" and "Y".
0N/A // When some tray icon gets removed from the tray, a Java icon may be repositioned.
0N/A // In this case the parent window also lose centering. We have to restore it.
0N/A
0N/A if (ex_height != 0) {
0N/A
1696N/A ctrLog.fine("ConfigureNotify on parent of {0}. Move detected. Centering by \"Y\".",
1696N/A XTrayIconPeer.this);
0N/A
0N/A XlibWrapper.XMoveWindow(XToolkit.getDisplay(), eframeParentID,
0N/A ce.get_x(),
0N/A ce.get_y() + ex_height/2 - TRAY_ICON_HEIGHT/2);
0N/A
0N/A } else if (ex_width != 0) {
0N/A
1696N/A ctrLog.fine("ConfigureNotify on parent of {0}. Move detected. Centering by \"X\".",
1696N/A XTrayIconPeer.this);
0N/A
0N/A XlibWrapper.XMoveWindow(XToolkit.getDisplay(), eframeParentID,
0N/A ce.get_x() + ex_width/2 - TRAY_ICON_WIDTH/2,
0N/A ce.get_y());
0N/A } else {
1696N/A ctrLog.fine("ConfigureNotify on parent of {0}. Move detected. Skipping.",
1696N/A XTrayIconPeer.this);
0N/A }
0N/A }
0N/A old_x = ce.get_x();
0N/A old_y = ce.get_y();
0N/A isParentWindowLocated = true;
0N/A }
0N/A };
0N/A }
0N/A eframeXED = new XEventDispatcher() {
0N/A // It's executed under AWTLock.
0N/A XTrayIconPeer xtiPeer = XTrayIconPeer.this;
0N/A
0N/A public void dispatchEvent(XEvent ev) {
216N/A if (isDisposed() || ev.get_type() != XConstants.ReparentNotify) {
0N/A return;
0N/A }
0N/A
0N/A XReparentEvent re = ev.get_xreparent();
0N/A eframeParentID = re.get_parent();
0N/A
0N/A if (eframeParentID == XToolkit.getDefaultRootWindow()) {
0N/A
0N/A if (isTrayIconDisplayed) { // most likely Notification Area was removed
0N/A SunToolkit.executeOnEventHandlerThread(xtiPeer.target, new Runnable() {
0N/A public void run() {
0N/A SystemTray.getSystemTray().remove(xtiPeer.target);
0N/A }
0N/A });
0N/A }
0N/A return;
0N/A }
0N/A
0N/A if (!isTrayIconDisplayed) {
216N/A addXED(eframeParentID, parentXED, XConstants.StructureNotifyMask);
0N/A
0N/A isTrayIconDisplayed = true;
0N/A XToolkit.awtLockNotifyAll();
0N/A }
0N/A }
0N/A };
0N/A
216N/A addXED(getWindow(), eframeXED, XConstants.StructureNotifyMask);
0N/A
0N/A XSystemTrayPeer.getPeerInstance().addTrayIcon(this); // throws AWTException
0N/A
0N/A // Wait till the EmbeddedFrame is reparented
0N/A long start = System.currentTimeMillis();
3876N/A final long PERIOD = XToolkit.getTrayIconDisplayTimeout();
0N/A XToolkit.awtLock();
0N/A try {
0N/A while (!isTrayIconDisplayed) {
0N/A try {
0N/A XToolkit.awtLockWait(PERIOD);
0N/A } catch (InterruptedException e) {
0N/A break;
0N/A }
0N/A if (System.currentTimeMillis() - start > PERIOD) {
0N/A break;
0N/A }
0N/A }
0N/A } finally {
0N/A XToolkit.awtUnlock();
0N/A }
0N/A
0N/A // This is unlikely to happen.
0N/A if (!isTrayIconDisplayed || eframeParentID == 0 ||
0N/A eframeParentID == XToolkit.getDefaultRootWindow())
0N/A {
0N/A throw new AWTException("TrayIcon couldn't be displayed.");
0N/A }
0N/A
0N/A eframe.setVisible(true);
0N/A updateImage();
0N/A
1066N/A balloon = new InfoWindow.Balloon(eframe, target, this);
1066N/A tooltip = new InfoWindow.Tooltip(eframe, target, this);
0N/A
0N/A addListeners();
0N/A }
0N/A
0N/A public void dispose() {
0N/A if (SunToolkit.isDispatchThreadForAppContext(target)) {
0N/A disposeOnEDT();
0N/A } else {
0N/A try {
0N/A SunToolkit.executeOnEDTAndWait(target, new Runnable() {
0N/A public void run() {
0N/A disposeOnEDT();
0N/A }
0N/A });
0N/A } catch (InterruptedException ie) {
0N/A } catch (InvocationTargetException ite) {}
0N/A }
0N/A }
0N/A
0N/A private void disposeOnEDT() {
0N/A // All actions that is to be synchronized with disposal
0N/A // should be executed either under AWTLock, or on EDT.
0N/A // isDisposed value must be checked.
0N/A XToolkit.awtLock();
0N/A isDisposed = true;
0N/A XToolkit.awtUnlock();
0N/A
0N/A removeXED(getWindow(), eframeXED);
0N/A removeXED(eframeParentID, parentXED);
0N/A eframe.realDispose();
0N/A balloon.dispose();
0N/A isTrayIconDisplayed = false;
0N/A XToolkit.targetDisposedPeer(target, this);
0N/A }
0N/A
0N/A public static void suppressWarningString(Window w) {
1976N/A AWTAccessor.getWindowAccessor().setTrayIconWindow(w, true);
0N/A }
0N/A
0N/A public void setToolTip(String tooltip) {
0N/A tooltipString = tooltip;
0N/A }
0N/A
1066N/A public String getTooltipString() {
1066N/A return tooltipString;
1066N/A }
1066N/A
0N/A public void updateImage() {
0N/A Runnable r = new Runnable() {
0N/A public void run() {
0N/A canvas.updateImage(target.getImage());
0N/A }
0N/A };
0N/A
0N/A if (!SunToolkit.isDispatchThreadForAppContext(target)) {
0N/A SunToolkit.executeOnEventHandlerThread(target, r);
0N/A } else {
0N/A r.run();
0N/A }
0N/A }
0N/A
0N/A public void displayMessage(String caption, String text, String messageType) {
0N/A Point loc = getLocationOnScreen();
0N/A Rectangle screen = eframe.getGraphicsConfiguration().getBounds();
0N/A
0N/A // Check if the tray icon is in the bounds of a screen.
0N/A if (!(loc.x < screen.x || loc.x >= screen.x + screen.width ||
0N/A loc.y < screen.y || loc.y >= screen.y + screen.height))
0N/A {
0N/A balloon.display(caption, text, messageType);
0N/A }
0N/A }
0N/A
0N/A // It's synchronized with disposal by EDT.
0N/A public void showPopupMenu(int x, int y) {
0N/A if (isDisposed())
0N/A return;
0N/A
0N/A assert SunToolkit.isDispatchThreadForAppContext(target);
0N/A
0N/A PopupMenu newPopup = target.getPopupMenu();
0N/A if (popup != newPopup) {
0N/A if (popup != null) {
0N/A eframe.remove(popup);
0N/A }
0N/A if (newPopup != null) {
0N/A eframe.add(newPopup);
0N/A }
0N/A popup = newPopup;
0N/A }
0N/A
0N/A if (popup != null) {
0N/A Point loc = ((XBaseWindow)eframe.getPeer()).toLocal(new Point(x, y));
0N/A popup.show(eframe, loc.x, loc.y);
0N/A }
0N/A }
0N/A
0N/A
0N/A // ******************************************************************
0N/A // ******************************************************************
0N/A
0N/A
0N/A private void addXED(long window, XEventDispatcher xed, long mask) {
0N/A if (window == 0) {
0N/A return;
0N/A }
0N/A XToolkit.awtLock();
0N/A try {
0N/A XlibWrapper.XSelectInput(XToolkit.getDisplay(), window, mask);
0N/A } finally {
0N/A XToolkit.awtUnlock();
0N/A }
0N/A XToolkit.addEventDispatcher(window, xed);
0N/A }
0N/A
0N/A private void removeXED(long window, XEventDispatcher xed) {
0N/A if (window == 0) {
0N/A return;
0N/A }
0N/A XToolkit.awtLock();
0N/A try {
0N/A XToolkit.removeEventDispatcher(window, xed);
0N/A } finally {
0N/A XToolkit.awtUnlock();
0N/A }
0N/A }
0N/A
0N/A // Private method for testing purposes.
0N/A private Point getLocationOnScreen() {
0N/A return eframe.getLocationOnScreen();
0N/A }
0N/A
1066N/A public Rectangle getBounds() {
0N/A Point loc = getLocationOnScreen();
0N/A return new Rectangle(loc.x, loc.y, loc.x + TRAY_ICON_WIDTH, loc.y + TRAY_ICON_HEIGHT);
0N/A }
0N/A
0N/A void addListeners() {
0N/A canvas.addMouseListener(eventProxy);
0N/A canvas.addMouseMotionListener(eventProxy);
0N/A }
0N/A
0N/A long getWindow() {
0N/A return ((XEmbeddedFramePeer)eframe.getPeer()).getWindow();
0N/A }
0N/A
1066N/A public boolean isDisposed() {
0N/A return isDisposed;
0N/A }
0N/A
1066N/A public String getActionCommand() {
1066N/A return target.getActionCommand();
1066N/A }
1066N/A
0N/A static class TrayIconEventProxy implements MouseListener, MouseMotionListener {
0N/A XTrayIconPeer xtiPeer;
0N/A
0N/A TrayIconEventProxy(XTrayIconPeer xtiPeer) {
0N/A this.xtiPeer = xtiPeer;
0N/A }
0N/A
0N/A public void handleEvent(MouseEvent e) {
0N/A //prevent DRAG events from being posted with TrayIcon source(CR 6565779)
0N/A if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
0N/A return;
0N/A }
0N/A
0N/A // Event handling is synchronized with disposal by EDT.
0N/A if (xtiPeer.isDisposed()) {
0N/A return;
0N/A }
0N/A Point coord = XBaseWindow.toOtherWindow(xtiPeer.getWindow(),
0N/A XToolkit.getDefaultRootWindow(),
0N/A e.getX(), e.getY());
0N/A
0N/A if (e.isPopupTrigger()) {
0N/A xtiPeer.showPopupMenu(coord.x, coord.y);
0N/A }
0N/A
0N/A e.translatePoint(coord.x - e.getX(), coord.y - e.getY());
0N/A // This is a hack in order to set non-Component source to MouseEvent
0N/A // instance.
0N/A // In some cases this could lead to unpredictable result (e.g. when
0N/A // other class tries to cast source field to Component).
0N/A // We already filter DRAG events out (CR 6565779).
0N/A e.setSource(xtiPeer.target);
0N/A Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(e);
0N/A }
0N/A public void mouseClicked(MouseEvent e) {
0N/A if ((e.getClickCount() > 1 || xtiPeer.balloon.isVisible()) &&
0N/A e.getButton() == MouseEvent.BUTTON1)
0N/A {
0N/A ActionEvent aev = new ActionEvent(xtiPeer.target, ActionEvent.ACTION_PERFORMED,
0N/A xtiPeer.target.getActionCommand(), e.getWhen(),
0N/A e.getModifiers());
2860N/A XToolkit.postEvent(XToolkit.targetToAppContext(aev.getSource()), aev);
0N/A }
0N/A if (xtiPeer.balloon.isVisible()) {
0N/A xtiPeer.balloon.hide();
0N/A }
0N/A handleEvent(e);
0N/A }
0N/A public void mouseEntered(MouseEvent e) {
0N/A xtiPeer.tooltip.enter();
0N/A handleEvent(e);
0N/A }
0N/A public void mouseExited(MouseEvent e) {
0N/A xtiPeer.tooltip.exit();
0N/A handleEvent(e);
0N/A }
0N/A public void mousePressed(MouseEvent e) {
0N/A handleEvent(e);
0N/A }
0N/A public void mouseReleased(MouseEvent e) {
0N/A handleEvent(e);
0N/A }
0N/A public void mouseDragged(MouseEvent e) {
0N/A handleEvent(e);
0N/A }
0N/A public void mouseMoved(MouseEvent e) {
0N/A handleEvent(e);
0N/A }
0N/A }
0N/A
0N/A // ***************************************
0N/A // Special embedded frame for tray icon
0N/A // ***************************************
0N/A
0N/A private static class XTrayIconEmbeddedFrame extends XEmbeddedFrame {
0N/A public XTrayIconEmbeddedFrame(){
0N/A super(XToolkit.getDefaultRootWindow(), true, true);
0N/A }
0N/A
0N/A public boolean isUndecorated() {
0N/A return true;
0N/A }
0N/A
0N/A public boolean isResizable() {
0N/A return false;
0N/A }
0N/A
0N/A // embedded frame for tray icon shouldn't be disposed by anyone except tray icon
0N/A public void dispose(){
0N/A }
0N/A
0N/A public void realDispose(){
0N/A super.dispose();
0N/A }
0N/A };
0N/A
0N/A // ***************************************
0N/A // Classes for painting an image on canvas
0N/A // ***************************************
0N/A
0N/A static class TrayIconCanvas extends IconCanvas {
0N/A TrayIcon target;
0N/A boolean autosize;
0N/A
0N/A TrayIconCanvas(TrayIcon target, int width, int height) {
0N/A super(width, height);
0N/A this.target = target;
0N/A }
0N/A
0N/A // Invoke on EDT.
0N/A protected void repaintImage(boolean doClear) {
0N/A boolean old_autosize = autosize;
0N/A autosize = target.isImageAutoSize();
0N/A
0N/A curW = autosize ? width : image.getWidth(observer);
0N/A curH = autosize ? height : image.getHeight(observer);
0N/A
0N/A super.repaintImage(doClear || (old_autosize != autosize));
0N/A }
0N/A }
0N/A
1066N/A public static class IconCanvas extends Canvas {
0N/A volatile Image image;
0N/A IconObserver observer;
0N/A int width, height;
0N/A int curW, curH;
0N/A
0N/A IconCanvas(int width, int height) {
0N/A this.width = curW = width;
0N/A this.height = curH = height;
0N/A }
0N/A
0N/A // Invoke on EDT.
0N/A public void updateImage(Image image) {
0N/A this.image = image;
0N/A if (observer == null) {
0N/A observer = new IconObserver();
0N/A }
0N/A repaintImage(true);
0N/A }
0N/A
0N/A // Invoke on EDT.
0N/A protected void repaintImage(boolean doClear) {
0N/A Graphics g = getGraphics();
0N/A if (g != null) {
0N/A try {
0N/A if (isVisible()) {
0N/A if (doClear) {
0N/A update(g);
0N/A } else {
0N/A paint(g);
0N/A }
0N/A }
0N/A } finally {
0N/A g.dispose();
0N/A }
0N/A }
0N/A }
0N/A
0N/A // Invoke on EDT.
0N/A public void paint(Graphics g) {
0N/A if (g != null && curW > 0 && curH > 0) {
0N/A BufferedImage bufImage = new BufferedImage(curW, curH, BufferedImage.TYPE_INT_ARGB);
0N/A Graphics2D gr = bufImage.createGraphics();
0N/A if (gr != null) {
0N/A try {
0N/A gr.setColor(getBackground());
0N/A gr.fillRect(0, 0, curW, curH);
0N/A gr.drawImage(image, 0, 0, curW, curH, observer);
0N/A gr.dispose();
0N/A
0N/A g.drawImage(bufImage, 0, 0, curW, curH, null);
0N/A } finally {
0N/A gr.dispose();
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A class IconObserver implements ImageObserver {
0N/A public boolean imageUpdate(final Image image, final int flags, int x, int y, int width, int height) {
0N/A if (image != IconCanvas.this.image || // if the image has been changed
0N/A !IconCanvas.this.isVisible())
0N/A {
0N/A return false;
0N/A }
0N/A if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS |
0N/A ImageObserver.WIDTH | ImageObserver.HEIGHT)) != 0)
0N/A {
0N/A SunToolkit.executeOnEventHandlerThread(IconCanvas.this, new Runnable() {
0N/A public void run() {
0N/A repaintImage(false);
0N/A }
0N/A });
0N/A }
0N/A return (flags & ImageObserver.ALLBITS) == 0;
0N/A }
0N/A }
0N/A }
0N/A}