1/*
2 * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.awt.X11;
27
28import java.awt.*;
29import java.awt.dnd.DropTarget;
30import java.awt.dnd.DropTargetListener;
31import java.awt.event.*;
32import sun.awt.*;
33import sun.awt.AWTAccessor;
34import sun.util.logging.PlatformLogger;
35import java.util.*;
36import static sun.awt.X11.XEmbedHelper.*;
37
38import java.security.AccessController;
39import sun.security.action.GetBooleanAction;
40
41public class XEmbedCanvasPeer extends XCanvasPeer implements WindowFocusListener, KeyEventPostProcessor, ModalityListener, WindowIDProvider {
42    private static final PlatformLogger xembedLog = PlatformLogger.getLogger("sun.awt.X11.xembed.XEmbedCanvasPeer");
43
44    boolean applicationActive; // Whether the application is active(has focus)
45    XEmbedServer xembed = new XEmbedServer(); // Helper object, contains XEmbed intrinsics
46    Map<Long, AWTKeyStroke> accelerators = new HashMap<Long, AWTKeyStroke>(); // Maps accelerator ID into AWTKeyStroke
47    Map<AWTKeyStroke, Long> accel_lookup = new HashMap<AWTKeyStroke, Long>(); // Maps AWTKeyStroke into accelerator ID
48    Set<GrabbedKey> grabbed_keys = new HashSet<GrabbedKey>(); // A set of keys grabbed by client
49    Object ACCEL_LOCK = accelerators; // Lock object for working with accelerators;
50    Object GRAB_LOCK = grabbed_keys; // Lock object for working with keys grabbed by client
51
52    XEmbedCanvasPeer() {}
53
54    XEmbedCanvasPeer(XCreateWindowParams params) {
55        super(params);
56    }
57
58    XEmbedCanvasPeer(Component target) {
59        super(target);
60    }
61
62    protected void postInit(XCreateWindowParams params) {
63        super.postInit(params);
64
65        installActivateListener();
66        installAcceleratorListener();
67        installModalityListener();
68
69        // XEmbed canvas should be non-traversable.
70        // FIXME: Probably should be removed and enforced setting of it by the users
71        target.setFocusTraversalKeysEnabled(false);
72    }
73
74    protected void preInit(XCreateWindowParams params) {
75        super.preInit(params);
76
77        params.put(EVENT_MASK,
78                   XConstants.KeyPressMask       | XConstants.KeyReleaseMask
79                   | XConstants.FocusChangeMask  | XConstants.ButtonPressMask | XConstants.ButtonReleaseMask
80                   | XConstants.EnterWindowMask  | XConstants.LeaveWindowMask | XConstants.PointerMotionMask
81                   | XConstants.ButtonMotionMask | XConstants.ExposureMask    | XConstants.StructureNotifyMask | XConstants.SubstructureNotifyMask);
82
83    }
84
85    void installModalityListener() {
86        ((SunToolkit)Toolkit.getDefaultToolkit()).addModalityListener(this);
87    }
88
89    void deinstallModalityListener() {
90        ((SunToolkit)Toolkit.getDefaultToolkit()).removeModalityListener(this);
91    }
92
93    void installAcceleratorListener() {
94        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(this);
95    }
96
97    void deinstallAcceleratorListener() {
98        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor(this);
99    }
100
101    void installActivateListener() {
102        // FIXME: should watch for hierarchy changes
103        Window toplevel = getTopLevel(target);
104        if (toplevel != null) {
105            toplevel.addWindowFocusListener(this);
106            applicationActive = toplevel.isFocused();
107        }
108    }
109
110    void deinstallActivateListener() {
111        Window toplevel = getTopLevel(target);
112        if (toplevel != null) {
113            toplevel.removeWindowFocusListener(this);
114        }
115    }
116
117    boolean isXEmbedActive() {
118        return xembed.handle != 0;
119    }
120
121    boolean isApplicationActive() {
122        return applicationActive;
123    }
124
125    void initDispatching() {
126        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
127            xembedLog.fine("Init embedding for " + Long.toHexString(xembed.handle));
128        }
129        XToolkit.awtLock();
130        try {
131            XToolkit.addEventDispatcher(xembed.handle, xembed);
132            XlibWrapper.XSelectInput(XToolkit.getDisplay(), xembed.handle,
133                                     XConstants.StructureNotifyMask | XConstants.PropertyChangeMask);
134
135            XDropTargetRegistry.getRegistry().registerXEmbedClient(getWindow(), xembed.handle);
136        } finally {
137            XToolkit.awtUnlock();
138        }
139        xembed.processXEmbedInfo();
140
141        notifyChildEmbedded();
142    }
143
144    void endDispatching() {
145        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
146            xembedLog.fine("End dispatching for " + Long.toHexString(xembed.handle));
147        }
148        XToolkit.awtLock();
149        try {
150            XDropTargetRegistry.getRegistry().unregisterXEmbedClient(getWindow(), xembed.handle);
151            // We can't deselect input since someone else might be interested in it
152            XToolkit.removeEventDispatcher(xembed.handle, xembed);
153        } finally {
154            XToolkit.awtUnlock();
155        }
156    }
157
158    void embedChild(long child) {
159        if (xembed.handle != 0) {
160            detachChild();
161        }
162        xembed.handle = child;
163        initDispatching();
164    }
165
166    void childDestroyed() {
167        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
168            xembedLog.fine("Child " + Long.toHexString(xembed.handle) + " has self-destroyed.");
169        }
170        endDispatching();
171        xembed.handle = 0;
172    }
173
174    public void handleEvent(AWTEvent e) {
175        super.handleEvent(e);
176        if (isXEmbedActive()) {
177            switch (e.getID()) {
178              case FocusEvent.FOCUS_GAINED:
179                  canvasFocusGained((FocusEvent)e);
180                  break;
181              case FocusEvent.FOCUS_LOST:
182                  canvasFocusLost((FocusEvent)e);
183                  break;
184              case KeyEvent.KEY_PRESSED:
185              case KeyEvent.KEY_RELEASED:
186                  if (!((InputEvent)e).isConsumed()) {
187                      forwardKeyEvent((KeyEvent)e);
188                  }
189                  break;
190            }
191        }
192    }
193
194    public void dispatchEvent(XEvent ev) {
195        super.dispatchEvent(ev);
196        switch (ev.get_type()) {
197          case XConstants.CreateNotify:
198              XCreateWindowEvent cr = ev.get_xcreatewindow();
199              if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
200                  xembedLog.finest("Message on embedder: " + cr);
201              }
202              if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
203                  xembedLog.finer("Create notify for parent " + Long.toHexString(cr.get_parent()) +
204                                  ", window " + Long.toHexString(cr.get_window()));
205              }
206              embedChild(cr.get_window());
207              break;
208          case XConstants.DestroyNotify:
209              XDestroyWindowEvent dn = ev.get_xdestroywindow();
210              if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
211                  xembedLog.finest("Message on embedder: " + dn);
212              }
213              if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
214                  xembedLog.finer("Destroy notify for parent: " + dn);
215              }
216              childDestroyed();
217              break;
218          case XConstants.ReparentNotify:
219              XReparentEvent rep = ev.get_xreparent();
220              if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
221                  xembedLog.finest("Message on embedder: " + rep);
222              }
223              if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
224                  xembedLog.finer("Reparent notify for parent " + Long.toHexString(rep.get_parent()) +
225                                  ", window " + Long.toHexString(rep.get_window()) +
226                                  ", event " + Long.toHexString(rep.get_event()));
227              }
228              if (rep.get_parent() == getWindow()) {
229                  // Reparented into us - embed it
230                  embedChild(rep.get_window());
231              } else {
232                  // Reparented out of us - detach it
233                  childDestroyed();
234              }
235              break;
236        }
237    }
238
239    public Dimension getPreferredSize() {
240        if (isXEmbedActive()) {
241            XToolkit.awtLock();
242            try {
243                long p_hints = XlibWrapper.XAllocSizeHints();
244                XSizeHints hints = new XSizeHints(p_hints);
245                XlibWrapper.XGetWMNormalHints(XToolkit.getDisplay(), xembed.handle, p_hints, XlibWrapper.larg1);
246                Dimension res = new Dimension(hints.get_width(), hints.get_height());
247                XlibWrapper.XFree(p_hints);
248                return res;
249            } finally {
250                XToolkit.awtUnlock();
251            }
252        } else {
253            return super.getPreferredSize();
254        }
255    }
256    public Dimension getMinimumSize() {
257        if (isXEmbedActive()) {
258            XToolkit.awtLock();
259            try {
260                long p_hints = XlibWrapper.XAllocSizeHints();
261                XSizeHints hints = new XSizeHints(p_hints);
262                XlibWrapper.XGetWMNormalHints(XToolkit.getDisplay(), xembed.handle, p_hints, XlibWrapper.larg1);
263                Dimension res = new Dimension(hints.get_min_width(), hints.get_min_height());
264                XlibWrapper.XFree(p_hints);
265                return res;
266            } finally {
267                XToolkit.awtUnlock();
268            }
269        } else {
270            return super.getMinimumSize();
271        }
272    }
273    public void dispose() {
274        if (isXEmbedActive()) {
275            detachChild();
276        }
277        deinstallActivateListener();
278        deinstallModalityListener();
279        deinstallAcceleratorListener();
280
281        // BUG: Focus traversal doesn't become enabled after the one round of embedding
282        //target.setFocusTraversalKeysEnabled(true);
283
284        super.dispose();
285    }
286
287    // Focusable is true in order to enable focus traversal through this Canvas
288    public boolean isFocusable() {
289        return true;
290    }
291
292    Window getTopLevel(Component comp) {
293        while (comp != null && !(comp instanceof Window)) {
294            comp = comp.getParent();
295        }
296        return (Window)comp;
297    }
298
299    Rectangle getClientBounds() {
300        XToolkit.awtLock();
301        try {
302            XWindowAttributes wattr = new XWindowAttributes();
303            try {
304                XErrorHandlerUtil.WITH_XERROR_HANDLER(XErrorHandler.IgnoreBadWindowHandler.getInstance());
305                int status = XlibWrapper.XGetWindowAttributes(XToolkit.getDisplay(),
306                                                              xembed.handle, wattr.pData);
307
308                XErrorHandlerUtil.RESTORE_XERROR_HANDLER();
309
310                if ((status == 0) ||
311                    ((XErrorHandlerUtil.saved_error != null) &&
312                    (XErrorHandlerUtil.saved_error.get_error_code() != XConstants.Success))) {
313                    return null;
314                }
315
316                return new Rectangle(wattr.get_x(), wattr.get_y(), wattr.get_width(), wattr.get_height());
317            } finally {
318                wattr.dispose();
319            }
320        } finally {
321            XToolkit.awtUnlock();
322        }
323    }
324
325    void childResized() {
326        if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
327            Rectangle bounds = getClientBounds();
328            xembedLog.finer("Child resized: " + bounds);
329            // It is not required to update embedder's size when client size changes
330            // However, since there is no any means to get client size it seems to be the
331            // only way to provide it. However, it contradicts with Java layout concept -
332            // so it is disabled for now.
333//             Rectangle my_bounds = getBounds();
334//             setBounds(my_bounds.x, my_bounds.y, bounds.width, bounds.height, SET_BOUNDS);
335        }
336        XToolkit.postEvent(XToolkit.targetToAppContext(target), new ComponentEvent(target, ComponentEvent.COMPONENT_RESIZED));
337    }
338
339    void focusNext() {
340        if (isXEmbedActive()) {
341            xembedLog.fine("Requesting focus for the next component after embedder");
342            postEvent(new InvocationEvent(target, new Runnable() {
343                    public void run() {
344                        KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(target);
345                    }
346                }));
347        } else {
348            xembedLog.fine("XEmbed is not active - denying focus next");
349        }
350    }
351
352    void focusPrev() {
353        if (isXEmbedActive()) {
354            xembedLog.fine("Requesting focus for the next component after embedder");
355            postEvent(new InvocationEvent(target, new Runnable() {
356                    public void run() {
357                        KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent(target);
358                    }
359                }));
360        } else {
361            xembedLog.fine("XEmbed is not active - denying focus prev");
362        }
363    }
364
365    void requestXEmbedFocus() {
366        if (isXEmbedActive()) {
367            xembedLog.fine("Requesting focus for client");
368            postEvent(new InvocationEvent(target, new Runnable() {
369                    public void run() {
370                        target.requestFocus();
371                    }
372                }));
373        } else {
374            xembedLog.fine("XEmbed is not active - denying request focus");
375        }
376    }
377
378    void notifyChildEmbedded() {
379        xembed.sendMessage(xembed.handle, XEMBED_EMBEDDED_NOTIFY, getWindow(), Math.min(xembed.version, XEMBED_VERSION), 0);
380        if (isApplicationActive()) {
381            xembedLog.fine("Sending WINDOW_ACTIVATE during initialization");
382            xembed.sendMessage(xembed.handle, XEMBED_WINDOW_ACTIVATE);
383            if (hasFocus()) {
384                xembedLog.fine("Sending FOCUS_GAINED during initialization");
385                xembed.sendMessage(xembed.handle, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0);
386            }
387        }
388    }
389
390    void detachChild() {
391        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
392            xembedLog.fine("Detaching child " + Long.toHexString(xembed.handle));
393        }
394        /**
395         *  XEmbed specification:
396         *  "The embedder can unmap the client and reparent the client window to the root window. If the
397         *  client receives an ReparentNotify event, it should check the parent field of the XReparentEvent
398         *  structure. If this is the root window of the window's screen, then the protocol is finished and
399         *  there is no further interaction. If it is a window other than the root window, then the protocol
400         *  continues with the new parent acting as the embedder window."
401         */
402        XToolkit.awtLock();
403        try {
404            XlibWrapper.XUnmapWindow(XToolkit.getDisplay(), xembed.handle);
405            XlibWrapper.XReparentWindow(XToolkit.getDisplay(), xembed.handle, XToolkit.getDefaultRootWindow(), 0, 0);
406        } finally {
407            XToolkit.awtUnlock();
408        }
409        endDispatching();
410        xembed.handle = 0;
411    }
412
413    public void windowGainedFocus(WindowEvent e) {
414        applicationActive = true;
415        if (isXEmbedActive()) {
416            xembedLog.fine("Sending WINDOW_ACTIVATE");
417            xembed.sendMessage(xembed.handle, XEMBED_WINDOW_ACTIVATE);
418        }
419    }
420
421    public void windowLostFocus(WindowEvent e) {
422        applicationActive = false;
423        if (isXEmbedActive()) {
424            xembedLog.fine("Sending WINDOW_DEACTIVATE");
425            xembed.sendMessage(xembed.handle, XEMBED_WINDOW_DEACTIVATE);
426        }
427    }
428
429    void canvasFocusGained(FocusEvent e) {
430        if (isXEmbedActive()) {
431            xembedLog.fine("Forwarding FOCUS_GAINED");
432            int flavor = XEMBED_FOCUS_CURRENT;
433            if (e.getCause() == FocusEvent.Cause.TRAVERSAL_FORWARD) {
434                flavor = XEMBED_FOCUS_FIRST;
435            } else if (e.getCause() == FocusEvent.Cause.TRAVERSAL_BACKWARD) {
436                flavor = XEMBED_FOCUS_LAST;
437            }
438            xembed.sendMessage(xembed.handle, XEMBED_FOCUS_IN, flavor, 0, 0);
439        }
440    }
441
442    void canvasFocusLost(FocusEvent e) {
443        if (isXEmbedActive() && !e.isTemporary()) {
444            xembedLog.fine("Forwarding FOCUS_LOST");
445            int num = 0;
446            if (AccessController.doPrivileged(new GetBooleanAction("sun.awt.xembed.testing"))) {
447                Component opp = e.getOppositeComponent();
448                try {
449                    num = Integer.parseInt(opp.getName());
450                } catch (NumberFormatException nfe) {
451                }
452            }
453            xembed.sendMessage(xembed.handle, XEMBED_FOCUS_OUT, num, 0, 0);
454        }
455    }
456
457    static byte[] getBData(KeyEvent e) {
458        return AWTAccessor.getAWTEventAccessor().getBData(e);
459    }
460
461    void forwardKeyEvent(KeyEvent e) {
462        xembedLog.fine("Try to forward key event");
463        byte[] bdata = getBData(e);
464        long data = Native.toData(bdata);
465        if (data == 0) {
466            return;
467        }
468        try {
469            XKeyEvent ke = new XKeyEvent(data);
470            ke.set_window(xembed.handle);
471            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
472                xembedLog.fine("Forwarding native key event: " + ke);
473            }
474            XToolkit.awtLock();
475            try {
476                XlibWrapper.XSendEvent(XToolkit.getDisplay(), xembed.handle, false, XConstants.NoEventMask, data);
477            } finally {
478                XToolkit.awtUnlock();
479            }
480        } finally {
481            XlibWrapper.unsafe.freeMemory(data);
482        }
483    }
484
485
486    /**
487     * Grab/ungrab key functionality is an unofficial API supported by
488     * GTK.  Unfortunately, it doesn't support accelerator API, so,
489     * since this is the ONLY shortcut-processing API available, we
490     * must support it.  See XEmbed.NON_STANDARD_XEMBED_GTK_*
491     * messages.  The format of these messages is as follows:
492     * - request from client:
493     * data[1] = NON_STANDARD_XEMBED_GTK_GRAB_KEY or NON_STANDARD_XEMBED_GTK_UNGRAB_KEY
494     * data[3] = X keysym
495     * data[4] = X modifiers
496     *
497     * - response from server (in case the grabbed key has been pressed):
498     * forwarded XKeyEvent that matches keysym/modifiers pair
499     */
500    void grabKey(final long keysym, final long modifiers) {
501        postEvent(new InvocationEvent(target, new Runnable() {
502                public void run() {
503                    GrabbedKey grab = new GrabbedKey(keysym, modifiers);
504                    if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
505                        xembedLog.fine("Grabbing key: " + grab);
506                    }
507                    synchronized(GRAB_LOCK) {
508                        grabbed_keys.add(grab);
509                    }
510                }
511            }));
512    }
513
514    void ungrabKey(final long keysym, final long modifiers) {
515        postEvent(new InvocationEvent(target, new Runnable() {
516                public void run() {
517                    GrabbedKey grab = new GrabbedKey(keysym, modifiers);
518                    if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
519                        xembedLog.fine("UnGrabbing key: " + grab);
520                    }
521                    synchronized(GRAB_LOCK) {
522                        grabbed_keys.remove(grab);
523                    }
524                }
525            }));
526    }
527
528    void registerAccelerator(final long accel_id, final long keysym, final long modifiers) {
529        postEvent(new InvocationEvent(target, new Runnable() {
530                public void run() {
531                    AWTKeyStroke stroke = xembed.getKeyStrokeForKeySym(keysym, modifiers);
532                    if (stroke != null) {
533                        if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
534                            xembedLog.fine("Registering accelerator " + accel_id + " for " + stroke);
535                        }
536                        synchronized(ACCEL_LOCK) {
537                            accelerators.put(accel_id, stroke);
538                            accel_lookup.put(stroke, accel_id);
539                        }
540                    }
541                    propogateRegisterAccelerator(stroke);
542                }
543            }));
544    }
545
546    void unregisterAccelerator(final long accel_id) {
547        postEvent(new InvocationEvent(target, new Runnable() {
548                public void run() {
549                    AWTKeyStroke stroke = null;
550                    synchronized(ACCEL_LOCK) {
551                        stroke = accelerators.get(accel_id);
552                        if (stroke != null) {
553                            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
554                                xembedLog.fine("Unregistering accelerator: " + accel_id);
555                            }
556                            accelerators.remove(accel_id);
557                            accel_lookup.remove(stroke); // FIXME: How about several accelerators with the same stroke?
558                        }
559                    }
560                    propogateUnRegisterAccelerator(stroke);
561                }
562            }));
563    }
564
565    void propogateRegisterAccelerator(AWTKeyStroke stroke) {
566        // Find the top-level and see if it is XEmbed client. If so, ask him to
567        // register the accelerator
568        XWindowPeer parent = getToplevelXWindow();
569        if (parent != null && parent instanceof XEmbeddedFramePeer) {
570            XEmbeddedFramePeer embedded = (XEmbeddedFramePeer)parent;
571            embedded.registerAccelerator(stroke);
572        }
573    }
574
575    void propogateUnRegisterAccelerator(AWTKeyStroke stroke) {
576        // Find the top-level and see if it is XEmbed client. If so, ask him to
577        // register the accelerator
578        XWindowPeer parent = getToplevelXWindow();
579        if (parent != null && parent instanceof XEmbeddedFramePeer) {
580            XEmbeddedFramePeer embedded = (XEmbeddedFramePeer)parent;
581            embedded.unregisterAccelerator(stroke);
582        }
583    }
584
585    public boolean postProcessKeyEvent(KeyEvent e) {
586        // Processing events only if we are in the focused window but
587        // we are not focus owner since otherwise we will get
588        // duplicate shortcut events in the client - one is from
589        // activate_accelerator, another from forwarded event
590        // FIXME: This is probably an incompatibility, protocol
591        // doesn't say anything about disable accelerators when client
592        // is focused.
593
594        XWindowPeer parent = getToplevelXWindow();
595        if (parent == null || !((Window)parent.getTarget()).isFocused() || target.isFocusOwner()) {
596            return false;
597        }
598
599        boolean result = false;
600
601        if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
602            xembedLog.finer("Post-processing event " + e);
603        }
604
605        // Process ACCELERATORS
606        AWTKeyStroke stroke = AWTKeyStroke.getAWTKeyStrokeForEvent(e);
607        long accel_id = 0;
608        boolean exists = false;
609        synchronized(ACCEL_LOCK) {
610            exists = accel_lookup.containsKey(stroke);
611            if (exists) {
612                accel_id = accel_lookup.get(stroke).longValue();
613            }
614        }
615        if (exists) {
616            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
617                xembedLog.fine("Activating accelerator " + accel_id);
618            }
619            xembed.sendMessage(xembed.handle, XEMBED_ACTIVATE_ACCELERATOR, accel_id, 0, 0); // FIXME: How about overloaded?
620            result = true;
621        }
622
623        // Process Grabs, unofficial GTK feature
624        exists = false;
625        GrabbedKey key = new GrabbedKey(e);
626        synchronized(GRAB_LOCK) {
627            exists = grabbed_keys.contains(key);
628        }
629        if (exists) {
630            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
631                xembedLog.fine("Forwarding grabbed key " + e);
632            }
633            forwardKeyEvent(e);
634            result = true;
635        }
636
637        return result;
638    }
639
640    public void modalityPushed(ModalityEvent ev) {
641        xembed.sendMessage(xembed.handle, XEMBED_MODALITY_ON);
642    }
643
644    public void modalityPopped(ModalityEvent ev) {
645        xembed.sendMessage(xembed.handle, XEMBED_MODALITY_OFF);
646    }
647
648    public void handleClientMessage(XEvent xev) {
649        super.handleClientMessage(xev);
650        XClientMessageEvent msg = xev.get_xclient();
651        if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
652            xembedLog.finer("Client message to embedder: " + msg);
653        }
654        if (msg.get_message_type() == XEmbedHelper.XEmbed.getAtom()) {
655            if (xembedLog.isLoggable(PlatformLogger.Level.FINE)) {
656                xembedLog.fine(XEmbedHelper.XEmbedMessageToString(msg));
657            }
658        }
659        if (isXEmbedActive()) {
660            switch ((int)msg.get_data(1)) {
661              case XEMBED_REQUEST_FOCUS:
662                  requestXEmbedFocus();
663                  break;
664              case XEMBED_FOCUS_NEXT:
665                  focusNext();
666                  break;
667              case XEMBED_FOCUS_PREV:
668                  focusPrev();
669                  break;
670              case XEMBED_REGISTER_ACCELERATOR:
671                  registerAccelerator(msg.get_data(2), msg.get_data(3), msg.get_data(4));
672                  break;
673              case XEMBED_UNREGISTER_ACCELERATOR:
674                  unregisterAccelerator(msg.get_data(2));
675                  break;
676              case NON_STANDARD_XEMBED_GTK_GRAB_KEY:
677                  grabKey(msg.get_data(3), msg.get_data(4));
678                  break;
679              case NON_STANDARD_XEMBED_GTK_UNGRAB_KEY:
680                  ungrabKey(msg.get_data(3), msg.get_data(4));
681                  break;
682            }
683        } else {
684            xembedLog.finer("But XEmbed is not Active!");
685        }
686    }
687
688    @SuppressWarnings("serial") // JDK-implementation class
689    private static class XEmbedDropTarget extends DropTarget {
690        public void addDropTargetListener(DropTargetListener dtl)
691          throws TooManyListenersException {
692            // Drop target listeners registered with this target will never be
693            // notified, since all drag notifications are routed to the XEmbed
694            // client. To avoid confusion we prohibit listeners registration
695            // by throwing TooManyListenersException as if there is a listener
696            // registered with this target already.
697            throw new TooManyListenersException();
698        }
699    }
700
701    public void setXEmbedDropTarget() {
702        // Register a drop site on the top level.
703        Runnable r = new Runnable() {
704                public void run() {
705                    target.setDropTarget(new XEmbedDropTarget());
706                }
707            };
708        SunToolkit.executeOnEventHandlerThread(target, r);
709    }
710
711    public void removeXEmbedDropTarget() {
712        // Unregister a drop site on the top level.
713        Runnable r = new Runnable() {
714                public void run() {
715                    if (target.getDropTarget() instanceof XEmbedDropTarget) {
716                        target.setDropTarget(null);
717                    }
718                }
719            };
720        SunToolkit.executeOnEventHandlerThread(target, r);
721    }
722
723    public boolean processXEmbedDnDEvent(long ctxt, int eventID) {
724        if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
725            xembedLog.finest("     Drop target=" + target.getDropTarget());
726        }
727        if (target.getDropTarget() instanceof XEmbedDropTarget) {
728            AppContext appContext = XToolkit.targetToAppContext(getTarget());
729            XDropTargetContextPeer peer =
730                XDropTargetContextPeer.getPeer(appContext);
731            peer.forwardEventToEmbedded(xembed.handle, ctxt, eventID);
732            return true;
733        } else {
734            return false;
735        }
736    }
737
738    class XEmbedServer extends XEmbedHelper implements XEventDispatcher {
739        long handle; // Handle to XEmbed client
740        long version;
741        long flags;
742
743        boolean processXEmbedInfo() {
744            long xembed_info_data = Native.allocateLongArray(2);
745            try {
746                if (!XEmbedInfo.getAtomData(handle, xembed_info_data, 2)) {
747                    // No more XEMBED_INFO? This is not XEmbed client!
748                    // Unfortunately this is the initial state of the most clients
749                    // FIXME: add 5-state processing
750                    //childDestroyed();
751                    xembedLog.finer("Unable to get XEMBED_INFO atom data");
752                    return false;
753                }
754                version = Native.getCard32(xembed_info_data, 0);
755                flags = Native.getCard32(xembed_info_data, 1);
756                boolean new_mapped = (flags & XEMBED_MAPPED) != 0;
757                boolean currently_mapped = XlibUtil.getWindowMapState(handle) != XConstants.IsUnmapped;
758                if (new_mapped != currently_mapped) {
759                    if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
760                        xembedLog.finer("Mapping state of the client has changed, old state: " + currently_mapped + ", new state: " + new_mapped);
761                    }
762                    if (new_mapped) {
763                        XToolkit.awtLock();
764                        try {
765                            XlibWrapper.XMapWindow(XToolkit.getDisplay(), handle);
766                        } finally {
767                            XToolkit.awtUnlock();
768                        }
769                    } else {
770                        XToolkit.awtLock();
771                        try {
772                            XlibWrapper.XUnmapWindow(XToolkit.getDisplay(), handle);
773                        } finally {
774                            XToolkit.awtUnlock();
775                        }
776                    }
777                } else {
778                    if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
779                        xembedLog.finer("Mapping state didn't change, mapped: " + currently_mapped);
780                    }
781                }
782                return true;
783            } finally {
784                XlibWrapper.unsafe.freeMemory(xembed_info_data);
785            }
786        }
787
788        public void handlePropertyNotify(XEvent xev) {
789            if (isXEmbedActive()) {
790                XPropertyEvent ev = xev.get_xproperty();
791                if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
792                    xembedLog.finer("Property change on client: " + ev);
793                }
794                if (ev.get_atom() == XAtom.XA_WM_NORMAL_HINTS) {
795                    childResized();
796                } else if (ev.get_atom() == XEmbedInfo.getAtom()) {
797                    processXEmbedInfo();
798                } else if (ev.get_atom() ==
799                           XDnDConstants.XA_XdndAware.getAtom()) {
800                    XDropTargetRegistry.getRegistry().unregisterXEmbedClient(getWindow(),
801                                                                             xembed.handle);
802                    if (ev.get_state() == XConstants.PropertyNewValue) {
803                        XDropTargetRegistry.getRegistry().registerXEmbedClient(getWindow(),
804                                                                                xembed.handle);
805                    }
806                }
807            } else {
808                xembedLog.finer("XEmbed is not active");
809            }
810        }
811        void handleConfigureNotify(XEvent xev) {
812            if (isXEmbedActive()) {
813                XConfigureEvent ev = xev.get_xconfigure();
814                if (xembedLog.isLoggable(PlatformLogger.Level.FINER)) {
815                    xembedLog.finer("Bounds change on client: " + ev);
816                }
817                if (xev.get_xany().get_window() == handle) {
818                    childResized();
819                }
820            }
821        }
822        public void dispatchEvent(XEvent xev) {
823            int type = xev.get_type();
824            switch (type) {
825              case XConstants.PropertyNotify:
826                  handlePropertyNotify(xev);
827                  break;
828              case XConstants.ConfigureNotify:
829                  handleConfigureNotify(xev);
830                  break;
831              case XConstants.ClientMessage:
832                  handleClientMessage(xev);
833                  break;
834            }
835        }
836    }
837
838    static class GrabbedKey {
839        long keysym;
840        long modifiers;
841        GrabbedKey(long keysym, long modifiers) {
842            this.keysym = keysym;
843            this.modifiers = modifiers;
844        }
845
846        GrabbedKey(KeyEvent ev) {
847            init(ev);
848        }
849
850        private void init(KeyEvent e) {
851            byte[] bdata = getBData(e);
852            long data = Native.toData(bdata);
853            if (data == 0) {
854                return;
855            }
856            try {
857                XToolkit.awtLock();
858                try {
859                    keysym = XWindow.getKeySymForAWTKeyCode(e.getKeyCode());
860                } finally {
861                    XToolkit.awtUnlock();
862                }
863                XKeyEvent ke = new XKeyEvent(data);
864
865                // We recognize only these masks
866                modifiers = ke.get_state() & (XConstants.ShiftMask | XConstants.ControlMask | XConstants.LockMask);
867                if (xembedLog.isLoggable(PlatformLogger.Level.FINEST)) {
868                    xembedLog.finest("Mapped " + e + " to " + this);
869                }
870            } finally {
871                XlibWrapper.unsafe.freeMemory(data);
872            }
873        }
874
875        public int hashCode() {
876            return (int)keysym & 0xFFFFFFFF;
877        }
878
879        public boolean equals(Object o) {
880            if (!(o instanceof GrabbedKey)) {
881                return false;
882            }
883            GrabbedKey key = (GrabbedKey)o;
884            return (keysym == key.keysym && modifiers == key.modifiers);
885        }
886
887        public String toString() {
888            return "Key combination[keysym=" + keysym + ", mods=" + modifiers + "]";
889        }
890    }
891}
892