/*
 * Copyright (c) 2003, 2013, 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 sun.awt.X11;

import java.awt.*;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.awt.event.*;
import sun.awt.*;
import sun.awt.AWTAccessor;
import sun.util.logging.PlatformLogger;
import java.util.*;
import static sun.awt.X11.XEmbedHelper.*;

import java.security.AccessController;
import sun.security.action.GetBooleanAction;

public class XEmbedCanvasPeer extends XCanvasPeer implements WindowFocusListener, KeyEventPostProcessor, ModalityListener, WindowIDProvider {
    private static final PlatformLogger xembedLog = PlatformLogger.getLogger("sun.awt.X11.xembed.XEmbedCanvasPeer");

    boolean applicationActive; // Whether the application is active(has focus)
    XEmbedServer xembed = new XEmbedServer(); // Helper object, contains XEmbed intrinsics
    Map<Long, AWTKeyStroke> accelerators = new HashMap<Long, AWTKeyStroke>(); // Maps accelerator ID into AWTKeyStroke
    Map<AWTKeyStroke, Long> accel_lookup = new HashMap<AWTKeyStroke, Long>(); // Maps AWTKeyStroke into accelerator ID
    Set<GrabbedKey> grabbed_keys = new HashSet<GrabbedKey>(); // A set of keys grabbed by client
    Object ACCEL_LOCK = accelerators; // Lock object for working with accelerators;
    Object GRAB_LOCK = grabbed_keys; // Lock object for working with keys grabbed by client

    XEmbedCanvasPeer() {}

    XEmbedCanvasPeer(XCreateWindowParams params) {
        super(params);
    }

    XEmbedCanvasPeer(Component target) {
        super(target);
    }

    protected void postInit(XCreateWindowParams params) {
        super.postInit(params);

        installActivateListener();
        installAcceleratorListener();
        installModalityListener();

        // XEmbed canvas should be non-traversable.
        // FIXME: Probably should be removed and enforced setting of it by the users
        target.setFocusTraversalKeysEnabled(false);
    }

    protected void preInit(XCreateWindowParams params) {
        super.preInit(params);

        params.put(EVENT_MASK,
                   XConstants.KeyPressMask       | XConstants.KeyReleaseMask
                   | XConstants.FocusChangeMask  | XConstants.ButtonPressMask | XConstants.ButtonReleaseMask
                   | XConstants.EnterWindowMask  | XConstants.LeaveWindowMask | XConstants.PointerMotionMask
                   | XConstants.ButtonMotionMask | XConstants.ExposureMask    | XConstants.StructureNotifyMask | XConstants.SubstructureNotifyMask);

    }

    void installModalityListener() {
        ((SunToolkit)Toolkit.getDefaultToolkit()).addModalityListener(this);
    }

    void deinstallModalityListener() {
        ((SunToolkit)Toolkit.getDefaultToolkit()).removeModalityListener(this);
    }

    void installAcceleratorListener() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(this);
    }

    void deinstallAcceleratorListener() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor(this);
    }

    void installActivateListener() {
        // FIXME: should watch for hierarchy changes
        Window toplevel = getTopLevel(target);
        if (toplevel != null) {
            toplevel.addWindowFocusListener(this);
            applicationActive = toplevel.isFocused();
        }
    }

    void deinstallActivateListener() {
        Window toplevel = getTopLevel(target);
        if (toplevel != null) {
            toplevel.removeWindowFocusListener(this);
        }
    }

    boolean isXEmbedActive() {
        return xembed.handle != 0;
    }

    boolean isApplicationActive() {
        return applicationActive;
    }

    void initDispatching() {
        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
            xembedLog.fine("Init embedding for " + Long.toHexString(xembed.handle));
        }
        XToolkit.awtLock();
        try {
            XToolkit.addEventDispatcher(xembed.handle, xembed);
            XlibWrapper.XSelectInput(XToolkit.getDisplay(), xembed.handle,
                                     XConstants.StructureNotifyMask | XConstants.PropertyChangeMask);

            XDropTargetRegistry.getRegistry().registerXEmbedClient(getWindow(), xembed.handle);
        } finally {
            XToolkit.awtUnlock();
        }
        xembed.processXEmbedInfo();

        notifyChildEmbedded();
    }

    void endDispatching() {
        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
            xembedLog.fine("End dispatching for " + Long.toHexString(xembed.handle));
        }
        XToolkit.awtLock();
        try {
            XDropTargetRegistry.getRegistry().unregisterXEmbedClient(getWindow(), xembed.handle);
            // We can't deselect input since someone else might be interested in it
            XToolkit.removeEventDispatcher(xembed.handle, xembed);
        } finally {
            XToolkit.awtUnlock();
        }
    }

    void embedChild(long child) {
        if (xembed.handle != 0) {
            detachChild();
        }
        xembed.handle = child;
        initDispatching();
    }

    void childDestroyed() {
        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
            xembedLog.fine("Child " + Long.toHexString(xembed.handle) + " has self-destroyed.");
        }
        endDispatching();
        xembed.handle = 0;
    }

    public void handleEvent(AWTEvent e) {
        super.handleEvent(e);
        if (isXEmbedActive()) {
            switch (e.getID()) {
              case FocusEvent.FOCUS_GAINED:
                  canvasFocusGained((FocusEvent)e);
                  break;
              case FocusEvent.FOCUS_LOST:
                  canvasFocusLost((FocusEvent)e);
                  break;
              case KeyEvent.KEY_PRESSED:
              case KeyEvent.KEY_RELEASED:
                  if (!((InputEvent)e).isConsumed()) {
                      forwardKeyEvent((KeyEvent)e);
                  }
                  break;
            }
        }
    }

    public void dispatchEvent(XEvent ev) {
        super.dispatchEvent(ev);
        switch (ev.get_type()) {
          case XConstants.CreateNotify:
              XCreateWindowEvent cr = ev.get_xcreatewindow();
              if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
                  xembedLog.finest("Message on embedder: " + cr);
              }
              if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
                  xembedLog.finer("Create notify for parent " + Long.toHexString(cr.get_parent()) +
                                  ", window " + Long.toHexString(cr.get_window()));
              }
              embedChild(cr.get_window());
              break;
          case XConstants.DestroyNotify:
              XDestroyWindowEvent dn = ev.get_xdestroywindow();
              if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
                  xembedLog.finest("Message on embedder: " + dn);
              }
              if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
                  xembedLog.finer("Destroy notify for parent: " + dn);
              }
              childDestroyed();
              break;
          case XConstants.ReparentNotify:
              XReparentEvent rep = ev.get_xreparent();
              if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
                  xembedLog.finest("Message on embedder: " + rep);
              }
              if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
                  xembedLog.finer("Reparent notify for parent " + Long.toHexString(rep.get_parent()) +
                                  ", window " + Long.toHexString(rep.get_window()) +
                                  ", event " + Long.toHexString(rep.get_event()));
              }
              if (rep.get_parent() == getWindow()) {
                  // Reparented into us - embed it
                  embedChild(rep.get_window());
              } else {
                  // Reparented out of us - detach it
                  childDestroyed();
              }
              break;
        }
    }

    public Dimension getPreferredSize() {
        if (isXEmbedActive()) {
            XToolkit.awtLock();
            try {
                long p_hints = XlibWrapper.XAllocSizeHints();
                XSizeHints hints = new XSizeHints(p_hints);
                XlibWrapper.XGetWMNormalHints(XToolkit.getDisplay(), xembed.handle, p_hints, XlibWrapper.larg1);
                Dimension res = new Dimension(hints.get_width(), hints.get_height());
                XlibWrapper.XFree(p_hints);
                return res;
            } finally {
                XToolkit.awtUnlock();
            }
        } else {
            return super.getPreferredSize();
        }
    }
    public Dimension getMinimumSize() {
        if (isXEmbedActive()) {
            XToolkit.awtLock();
            try {
                long p_hints = XlibWrapper.XAllocSizeHints();
                XSizeHints hints = new XSizeHints(p_hints);
                XlibWrapper.XGetWMNormalHints(XToolkit.getDisplay(), xembed.handle, p_hints, XlibWrapper.larg1);
                Dimension res = new Dimension(hints.get_min_width(), hints.get_min_height());
                XlibWrapper.XFree(p_hints);
                return res;
            } finally {
                XToolkit.awtUnlock();
            }
        } else {
            return super.getMinimumSize();
        }
    }
    public void dispose() {
        if (isXEmbedActive()) {
            detachChild();
        }
        deinstallActivateListener();
        deinstallModalityListener();
        deinstallAcceleratorListener();

        // BUG: Focus traversal doesn't become enabled after the one round of embedding
        //target.setFocusTraversalKeysEnabled(true);

        super.dispose();
    }

    // Focusable is true in order to enable focus traversal through this Canvas
    public boolean isFocusable() {
        return true;
    }

    Window getTopLevel(Component comp) {
        while (comp != null && !(comp instanceof Window)) {
            comp = comp.getParent();
        }
        return (Window)comp;
    }

    Rectangle getClientBounds() {
        XToolkit.awtLock();
        try {
            XWindowAttributes wattr = new XWindowAttributes();
            try {
                XErrorHandlerUtil.WITH_XERROR_HANDLER(XErrorHandler.IgnoreBadWindowHandler.getInstance());
                int status = XlibWrapper.XGetWindowAttributes(XToolkit.getDisplay(),
                                                              xembed.handle, wattr.pData);

                XErrorHandlerUtil.RESTORE_XERROR_HANDLER();

                if ((status == 0) ||
                    ((XErrorHandlerUtil.saved_error != null) &&
                    (XErrorHandlerUtil.saved_error.get_error_code() != XConstants.Success))) {
                    return null;
                }

                return new Rectangle(wattr.get_x(), wattr.get_y(), wattr.get_width(), wattr.get_height());
            } finally {
                wattr.dispose();
            }
        } finally {
            XToolkit.awtUnlock();
        }
    }

    void childResized() {
        if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
            Rectangle bounds = getClientBounds();
            xembedLog.finer("Child resized: " + bounds);
            // It is not required to update embedder's size when client size changes
            // However, since there is no any means to get client size it seems to be the
            // only way to provide it. However, it contradicts with Java layout concept -
            // so it is disabled for now.
//             Rectangle my_bounds = getBounds();
//             setBounds(my_bounds.x, my_bounds.y, bounds.width, bounds.height, SET_BOUNDS);
        }
        XToolkit.postEvent(XToolkit.targetToAppContext(target), new ComponentEvent(target, ComponentEvent.COMPONENT_RESIZED));
    }

    void focusNext() {
        if (isXEmbedActive()) {
            xembedLog.fine("Requesting focus for the next component after embedder");
            postEvent(new InvocationEvent(target, new Runnable() {
                    public void run() {
                        KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(target);
                    }
                }));
        } else {
            xembedLog.fine("XEmbed is not active - denying focus next");
        }
    }

    void focusPrev() {
        if (isXEmbedActive()) {
            xembedLog.fine("Requesting focus for the next component after embedder");
            postEvent(new InvocationEvent(target, new Runnable() {
                    public void run() {
                        KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent(target);
                    }
                }));
        } else {
            xembedLog.fine("XEmbed is not active - denying focus prev");
        }
    }

    void requestXEmbedFocus() {
        if (isXEmbedActive()) {
            xembedLog.fine("Requesting focus for client");
            postEvent(new InvocationEvent(target, new Runnable() {
                    public void run() {
                        target.requestFocus();
                    }
                }));
        } else {
            xembedLog.fine("XEmbed is not active - denying request focus");
        }
    }

    void notifyChildEmbedded() {
        xembed.sendMessage(xembed.handle, XEMBED_EMBEDDED_NOTIFY, getWindow(), Math.min(xembed.version, XEMBED_VERSION), 0);
        if (isApplicationActive()) {
            xembedLog.fine("Sending WINDOW_ACTIVATE during initialization");
            xembed.sendMessage(xembed.handle, XEMBED_WINDOW_ACTIVATE);
            if (hasFocus()) {
                xembedLog.fine("Sending FOCUS_GAINED during initialization");
                xembed.sendMessage(xembed.handle, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0);
            }
        }
    }

    void detachChild() {
        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
            xembedLog.fine("Detaching child " + Long.toHexString(xembed.handle));
        }
        /**
         *  XEmbed specification:
         *  "The embedder can unmap the client and reparent the client window to the root window. If the
         *  client receives an ReparentNotify event, it should check the parent field of the XReparentEvent
         *  structure. If this is the root window of the window's screen, then the protocol is finished and
         *  there is no further interaction. If it is a window other than the root window, then the protocol
         *  continues with the new parent acting as the embedder window."
         */
        XToolkit.awtLock();
        try {
            XlibWrapper.XUnmapWindow(XToolkit.getDisplay(), xembed.handle);
            XlibWrapper.XReparentWindow(XToolkit.getDisplay(), xembed.handle, XToolkit.getDefaultRootWindow(), 0, 0);
        } finally {
            XToolkit.awtUnlock();
        }
        endDispatching();
        xembed.handle = 0;
    }

    public void windowGainedFocus(WindowEvent e) {
        applicationActive = true;
        if (isXEmbedActive()) {
            xembedLog.fine("Sending WINDOW_ACTIVATE");
            xembed.sendMessage(xembed.handle, XEMBED_WINDOW_ACTIVATE);
        }
    }

    public void windowLostFocus(WindowEvent e) {
        applicationActive = false;
        if (isXEmbedActive()) {
            xembedLog.fine("Sending WINDOW_DEACTIVATE");
            xembed.sendMessage(xembed.handle, XEMBED_WINDOW_DEACTIVATE);
        }
    }

    void canvasFocusGained(FocusEvent e) {
        if (isXEmbedActive()) {
            xembedLog.fine("Forwarding FOCUS_GAINED");
            int flavor = XEMBED_FOCUS_CURRENT;
            if (e instanceof CausedFocusEvent) {
                CausedFocusEvent ce = (CausedFocusEvent)e;
                if (ce.getCause() == CausedFocusEvent.Cause.TRAVERSAL_FORWARD) {
                    flavor = XEMBED_FOCUS_FIRST;
                } else if (ce.getCause() == CausedFocusEvent.Cause.TRAVERSAL_BACKWARD) {
                    flavor = XEMBED_FOCUS_LAST;
                }
            }
            xembed.sendMessage(xembed.handle, XEMBED_FOCUS_IN, flavor, 0, 0);
        }
    }

    void canvasFocusLost(FocusEvent e) {
        if (isXEmbedActive() && !e.isTemporary()) {
            xembedLog.fine("Forwarding FOCUS_LOST");
            int num = 0;
            if (AccessController.doPrivileged(new GetBooleanAction("sun.awt.xembed.testing"))) {
                Component opp = e.getOppositeComponent();
                try {
                    num = Integer.parseInt(opp.getName());
                } catch (NumberFormatException nfe) {
                }
            }
            xembed.sendMessage(xembed.handle, XEMBED_FOCUS_OUT, num, 0, 0);
        }
    }

    static byte[] getBData(KeyEvent e) {
        return AWTAccessor.getAWTEventAccessor().getBData(e);
    }

    void forwardKeyEvent(KeyEvent e) {
        xembedLog.fine("Try to forward key event");
        byte[] bdata = getBData(e);
        long data = Native.toData(bdata);
        if (data == 0) {
            return;
        }
        try {
            XKeyEvent ke = new XKeyEvent(data);
            ke.set_window(xembed.handle);
            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
                xembedLog.fine("Forwarding native key event: " + ke);
            }
            XToolkit.awtLock();
            try {
                XlibWrapper.XSendEvent(XToolkit.getDisplay(), xembed.handle, false, XConstants.NoEventMask, data);
            } finally {
                XToolkit.awtUnlock();
            }
        } finally {
            XlibWrapper.unsafe.freeMemory(data);
        }
    }


    /**
     * Grab/ungrab key functionality is an unofficial API supported by
     * GTK.  Unfortunately, it doesn't support accelerator API, so,
     * since this is the ONLY shortcut-processing API available, we
     * must support it.  See XEmbed.NON_STANDARD_XEMBED_GTK_*
     * messages.  The format of these messages is as follows:
     * - request from client:
     * data[1] = NON_STANDARD_XEMBED_GTK_GRAB_KEY or NON_STANDARD_XEMBED_GTK_UNGRAB_KEY
     * data[3] = X keysym
     * data[4] = X modifiers
     *
     * - response from server (in case the grabbed key has been pressed):
     * forwarded XKeyEvent that matches keysym/modifiers pair
     */
    void grabKey(final long keysym, final long modifiers) {
        postEvent(new InvocationEvent(target, new Runnable() {
                public void run() {
                    GrabbedKey grab = new GrabbedKey(keysym, modifiers);
                    if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
                        xembedLog.fine("Grabbing key: " + grab);
                    }
                    synchronized(GRAB_LOCK) {
                        grabbed_keys.add(grab);
                    }
                }
            }));
    }

    void ungrabKey(final long keysym, final long modifiers) {
        postEvent(new InvocationEvent(target, new Runnable() {
                public void run() {
                    GrabbedKey grab = new GrabbedKey(keysym, modifiers);
                    if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
                        xembedLog.fine("UnGrabbing key: " + grab);
                    }
                    synchronized(GRAB_LOCK) {
                        grabbed_keys.remove(grab);
                    }
                }
            }));
    }

    void registerAccelerator(final long accel_id, final long keysym, final long modifiers) {
        postEvent(new InvocationEvent(target, new Runnable() {
                public void run() {
                    AWTKeyStroke stroke = xembed.getKeyStrokeForKeySym(keysym, modifiers);
                    if (stroke != null) {
                        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
                            xembedLog.fine("Registering accelerator " + accel_id + " for " + stroke);
                        }
                        synchronized(ACCEL_LOCK) {
                            accelerators.put(accel_id, stroke);
                            accel_lookup.put(stroke, accel_id);
                        }
                    }
                    propogateRegisterAccelerator(stroke);
                }
            }));
    }

    void unregisterAccelerator(final long accel_id) {
        postEvent(new InvocationEvent(target, new Runnable() {
                public void run() {
                    AWTKeyStroke stroke = null;
                    synchronized(ACCEL_LOCK) {
                        stroke = accelerators.get(accel_id);
                        if (stroke != null) {
                            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
                                xembedLog.fine("Unregistering accelerator: " + accel_id);
                            }
                            accelerators.remove(accel_id);
                            accel_lookup.remove(stroke); // FIXME: How about several accelerators with the same stroke?
                        }
                    }
                    propogateUnRegisterAccelerator(stroke);
                }
            }));
    }

    void propogateRegisterAccelerator(AWTKeyStroke stroke) {
        // Find the top-level and see if it is XEmbed client. If so, ask him to
        // register the accelerator
        XWindowPeer parent = getToplevelXWindow();
        if (parent != null && parent instanceof XEmbeddedFramePeer) {
            XEmbeddedFramePeer embedded = (XEmbeddedFramePeer)parent;
            embedded.registerAccelerator(stroke);
        }
    }

    void propogateUnRegisterAccelerator(AWTKeyStroke stroke) {
        // Find the top-level and see if it is XEmbed client. If so, ask him to
        // register the accelerator
        XWindowPeer parent = getToplevelXWindow();
        if (parent != null && parent instanceof XEmbeddedFramePeer) {
            XEmbeddedFramePeer embedded = (XEmbeddedFramePeer)parent;
            embedded.unregisterAccelerator(stroke);
        }
    }

    public boolean postProcessKeyEvent(KeyEvent e) {
        // Processing events only if we are in the focused window but
        // we are not focus owner since otherwise we will get
        // duplicate shortcut events in the client - one is from
        // activate_accelerator, another from forwarded event
        // FIXME: This is probably an incompatibility, protocol
        // doesn't say anything about disable accelerators when client
        // is focused.

        XWindowPeer parent = getToplevelXWindow();
        if (parent == null || !((Window)parent.getTarget()).isFocused() || target.isFocusOwner()) {
            return false;
        }

        boolean result = false;

        if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
            xembedLog.finer("Post-processing event " + e);
        }

        // Process ACCELERATORS
        AWTKeyStroke stroke = AWTKeyStroke.getAWTKeyStrokeForEvent(e);
        long accel_id = 0;
        boolean exists = false;
        synchronized(ACCEL_LOCK) {
            exists = accel_lookup.containsKey(stroke);
            if (exists) {
                accel_id = accel_lookup.get(stroke).longValue();
            }
        }
        if (exists) {
            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
                xembedLog.fine("Activating accelerator " + accel_id);
            }
            xembed.sendMessage(xembed.handle, XEMBED_ACTIVATE_ACCELERATOR, accel_id, 0, 0); // FIXME: How about overloaded?
            result = true;
        }

        // Process Grabs, unofficial GTK feature
        exists = false;
        GrabbedKey key = new GrabbedKey(e);
        synchronized(GRAB_LOCK) {
            exists = grabbed_keys.contains(key);
        }
        if (exists) {
            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
                xembedLog.fine("Forwarding grabbed key " + e);
            }
            forwardKeyEvent(e);
            result = true;
        }

        return result;
    }

    public void modalityPushed(ModalityEvent ev) {
        xembed.sendMessage(xembed.handle, XEMBED_MODALITY_ON);
    }

    public void modalityPopped(ModalityEvent ev) {
        xembed.sendMessage(xembed.handle, XEMBED_MODALITY_OFF);
    }

    public void handleClientMessage(XEvent xev) {
        super.handleClientMessage(xev);
        XClientMessageEvent msg = xev.get_xclient();
        if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
            xembedLog.finer("Client message to embedder: " + msg);
        }
        if (msg.get_message_type() == xembed.XEmbed.getAtom()) {
            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
                xembedLog.fine(xembed.XEmbedMessageToString(msg));
            }
        }
        if (isXEmbedActive()) {
            switch ((int)msg.get_data(1)) {
              case XEMBED_REQUEST_FOCUS:
                  requestXEmbedFocus();
                  break;
              case XEMBED_FOCUS_NEXT:
                  focusNext();
                  break;
              case XEMBED_FOCUS_PREV:
                  focusPrev();
                  break;
              case XEMBED_REGISTER_ACCELERATOR:
                  registerAccelerator(msg.get_data(2), msg.get_data(3), msg.get_data(4));
                  break;
              case XEMBED_UNREGISTER_ACCELERATOR:
                  unregisterAccelerator(msg.get_data(2));
                  break;
              case NON_STANDARD_XEMBED_GTK_GRAB_KEY:
                  grabKey(msg.get_data(3), msg.get_data(4));
                  break;
              case NON_STANDARD_XEMBED_GTK_UNGRAB_KEY:
                  ungrabKey(msg.get_data(3), msg.get_data(4));
                  break;
            }
        } else {
            xembedLog.finer("But XEmbed is not Active!");
        }
    }

    private static class XEmbedDropTarget extends DropTarget {
        public void addDropTargetListener(DropTargetListener dtl)
          throws TooManyListenersException {
            // Drop target listeners registered with this target will never be
            // notified, since all drag notifications are routed to the XEmbed
            // client. To avoid confusion we prohibit listeners registration
            // by throwing TooManyListenersException as if there is a listener
            // registered with this target already.
            throw new TooManyListenersException();
        }
    }

    public void setXEmbedDropTarget() {
        // Register a drop site on the top level.
        Runnable r = new Runnable() {
                public void run() {
                    target.setDropTarget(new XEmbedDropTarget());
                }
            };
        SunToolkit.executeOnEventHandlerThread(target, r);
    }

    public void removeXEmbedDropTarget() {
        // Unregister a drop site on the top level.
        Runnable r = new Runnable() {
                public void run() {
                    if (target.getDropTarget() instanceof XEmbedDropTarget) {
                        target.setDropTarget(null);
                    }
                }
            };
        SunToolkit.executeOnEventHandlerThread(target, r);
    }

    public boolean processXEmbedDnDEvent(long ctxt, int eventID) {
        if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
            xembedLog.finest("     Drop target=" + target.getDropTarget());
        }
        if (target.getDropTarget() instanceof XEmbedDropTarget) {
            AppContext appContext = XToolkit.targetToAppContext(getTarget());
            XDropTargetContextPeer peer =
                XDropTargetContextPeer.getPeer(appContext);
            peer.forwardEventToEmbedded(xembed.handle, ctxt, eventID);
            return true;
        } else {
            return false;
        }
    }

    class XEmbedServer extends XEmbedHelper implements XEventDispatcher {
        long handle; // Handle to XEmbed client
        long version;
        long flags;

        boolean processXEmbedInfo() {
            long xembed_info_data = Native.allocateLongArray(2);
            try {
                if (!XEmbedInfo.getAtomData(handle, xembed_info_data, 2)) {
                    // No more XEMBED_INFO? This is not XEmbed client!
                    // Unfortunately this is the initial state of the most clients
                    // FIXME: add 5-state processing
                    //childDestroyed();
                    xembedLog.finer("Unable to get XEMBED_INFO atom data");
                    return false;
                }
                version = Native.getCard32(xembed_info_data, 0);
                flags = Native.getCard32(xembed_info_data, 1);
                boolean new_mapped = (flags & XEMBED_MAPPED) != 0;
                boolean currently_mapped = XlibUtil.getWindowMapState(handle) != XConstants.IsUnmapped;
                if (new_mapped != currently_mapped) {
                    if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
                        xembedLog.finer("Mapping state of the client has changed, old state: " + currently_mapped + ", new state: " + new_mapped);
                    }
                    if (new_mapped) {
                        XToolkit.awtLock();
                        try {
                            XlibWrapper.XMapWindow(XToolkit.getDisplay(), handle);
                        } finally {
                            XToolkit.awtUnlock();
                        }
                    } else {
                        XToolkit.awtLock();
                        try {
                            XlibWrapper.XUnmapWindow(XToolkit.getDisplay(), handle);
                        } finally {
                            XToolkit.awtUnlock();
                        }
                    }
                } else {
                    if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
                        xembedLog.finer("Mapping state didn't change, mapped: " + currently_mapped);
                    }
                }
                return true;
            } finally {
                XlibWrapper.unsafe.freeMemory(xembed_info_data);
            }
        }

        public void handlePropertyNotify(XEvent xev) {
            if (isXEmbedActive()) {
                XPropertyEvent ev = xev.get_xproperty();
                if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
                    xembedLog.finer("Property change on client: " + ev);
                }
                if (ev.get_atom() == XAtom.XA_WM_NORMAL_HINTS) {
                    childResized();
                } else if (ev.get_atom() == XEmbedInfo.getAtom()) {
                    processXEmbedInfo();
                } else if (ev.get_atom() ==
                           XDnDConstants.XA_XdndAware.getAtom()) {
                    XDropTargetRegistry.getRegistry().unregisterXEmbedClient(getWindow(),
                                                                             xembed.handle);
                    if (ev.get_state() == XConstants.PropertyNewValue) {
                        XDropTargetRegistry.getRegistry().registerXEmbedClient(getWindow(),
                                                                                xembed.handle);
                    }
                }
            } else {
                xembedLog.finer("XEmbed is not active");
            }
        }
        void handleConfigureNotify(XEvent xev) {
            if (isXEmbedActive()) {
                XConfigureEvent ev = xev.get_xconfigure();
                if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
                    xembedLog.finer("Bounds change on client: " + ev);
                }
                if (xev.get_xany().get_window() == handle) {
                    childResized();
                }
            }
        }
        public void dispatchEvent(XEvent xev) {
            int type = xev.get_type();
            switch (type) {
              case XConstants.PropertyNotify:
                  handlePropertyNotify(xev);
                  break;
              case XConstants.ConfigureNotify:
                  handleConfigureNotify(xev);
                  break;
              case XConstants.ClientMessage:
                  handleClientMessage(xev);
                  break;
            }
        }
    }

    static class GrabbedKey {
        long keysym;
        long modifiers;
        GrabbedKey(long keysym, long modifiers) {
            this.keysym = keysym;
            this.modifiers = modifiers;
        }

        GrabbedKey(KeyEvent ev) {
            init(ev);
        }

        private void init(KeyEvent e) {
            byte[] bdata = getBData(e);
            long data = Native.toData(bdata);
            if (data == 0) {
                return;
            }
            try {
                XToolkit.awtLock();
                try {
                    keysym = XWindow.getKeySymForAWTKeyCode(e.getKeyCode());
                } finally {
                    XToolkit.awtUnlock();
                }
                XKeyEvent ke = new XKeyEvent(data);

                // We recognize only these masks
                modifiers = ke.get_state() & (XConstants.ShiftMask | XConstants.ControlMask | XConstants.LockMask);
                if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
                    xembedLog.finest("Mapped " + e + " to " + this);
                }
            } finally {
                XlibWrapper.unsafe.freeMemory(data);
            }
        }

        public int hashCode() {
            return (int)keysym & 0xFFFFFFFF;
        }

        public boolean equals(Object o) {
            if (!(o instanceof GrabbedKey)) {
                return false;
            }
            GrabbedKey key = (GrabbedKey)o;
            return (keysym == key.keysym && modifiers == key.modifiers);
        }

        public String toString() {
            return "Key combination[keysym=" + keysym + ", mods=" + modifiers + "]";
        }
    }
}
