/* * Copyright 2002-2009 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.awt.X11; import java.awt.*; import java.awt.event.ComponentEvent; import java.awt.event.InvocationEvent; import java.awt.event.WindowEvent; import java.util.logging.Level; import java.util.logging.Logger; import sun.awt.AWTAccessor; import sun.awt.ComponentAccessor; import sun.awt.SunToolkit; abstract class XDecoratedPeer extends XWindowPeer { private static final Logger log = Logger.getLogger("sun.awt.X11.XDecoratedPeer"); private static final Logger insLog = Logger.getLogger("sun.awt.X11.insets.XDecoratedPeer"); private static final Logger focusLog = Logger.getLogger("sun.awt.X11.focus.XDecoratedPeer"); private static final Logger iconLog = Logger.getLogger("sun.awt.X11.icon.XDecoratedPeer"); XIconWindow iconWindow; /** * The dimensions of the window. * * The entity encapsulates information about the bounds and the insets of * the window. The value is initialized in the preInit() method with the * bounds of the window as known on the shared level. Further updates * should be performed with the reportReshape() method only. */ protected WindowDimensions dimensions; XContentWindow content; XFocusProxyWindow focusProxy; XDecoratedPeer(Window target) { super(target); } XDecoratedPeer(XCreateWindowParams params) { super(params); } public long getShell() { return window; } public long getContentWindow() { return (content == null) ? window : content.getWindow(); } void preInit(XCreateWindowParams params) { super.preInit(params); winAttr.initialFocus = true; Rectangle bounds = (Rectangle)params.get(BOUNDS); dimensions = new WindowDimensions(bounds, getNativeInsets(), false); params.put(BOUNDS, dimensions.getClientRect()); insLog.log(Level.FINE, "Initial dimensions {0}", new Object[] { dimensions }); // Deny default processing of these events on the shell - proxy will take care of // them instead Long eventMask = (Long)params.get(EVENT_MASK); params.add(EVENT_MASK, Long.valueOf(eventMask.longValue() & ~(XConstants.FocusChangeMask | XConstants.KeyPressMask | XConstants.KeyReleaseMask))); } void postInit(XCreateWindowParams params) { super.postInit(params); // The lines that follow need to be in a postInit, so they // happen after the X window is created. initResizability(); updateSizeHints(dimensions); content = XContentWindow.createContent(this); if (warningWindow != null) { warningWindow.toFront(); } focusProxy = createFocusProxy(); } void setIconHints(java.util.List icons) { if (!XWM.getWM().setNetWMIcon(this, icons)) { if (icons.size() > 0) { if (iconWindow == null) { iconWindow = new XIconWindow(this); } iconWindow.setIconImages(icons); } } } public void updateMinimumSize() { super.updateMinimumSize(); updateMinSizeHints(getNativeInsets()); } private void updateMinSizeHints(Insets insets) { if (isResizable()) { Dimension minimumSize = getTargetMinimumSize(); if (minimumSize != null) { int minWidth = minimumSize.width - insets.left - insets.right; int minHeight = minimumSize.height - insets.top - insets.bottom; if (minWidth < 0) minWidth = 0; if (minHeight < 0) minHeight = 0; setSizeHints(XUtilConstants.PMinSize | (isLocationByPlatform()?0:(XUtilConstants.PPosition | XUtilConstants.USPosition)), getX(), getY(), minWidth, minHeight); if (isVisible()) { Rectangle bounds = getShellBounds(); int nw = (bounds.width < minWidth) ? minWidth : bounds.width; int nh = (bounds.height < minHeight) ? minHeight : bounds.height; if (nw != bounds.width || nh != bounds.height) { setShellSize(new Rectangle(0, 0, nw, nh)); } } } else { boolean isMinSizeSet = isMinSizeSet(); XWM.removeSizeHints(this, XUtilConstants.PMinSize); /* Some WMs need remap to redecorate the window */ if (isMinSizeSet && isShowing() && XWM.needRemap(this)) { /* * Do the re/mapping at the Xlib level. Since we essentially * work around a WM bug we don't want this hack to be exposed * to Intrinsics (i.e. don't mess with grabs, callbacks etc). */ xSetVisible(false); XToolkit.XSync(); xSetVisible(true); } } } } XFocusProxyWindow createFocusProxy() { return new XFocusProxyWindow(this); } protected XAtomList getWMProtocols() { XAtomList protocols = super.getWMProtocols(); protocols.add(wm_delete_window); protocols.add(wm_take_focus); return protocols; } public Graphics getGraphics() { return getGraphics(content.surfaceData, ComponentAccessor.getForeground(target), ComponentAccessor.getBackground(target), ComponentAccessor.getFont_NoClientCode(target)); } public void setTitle(String title) { if (log.isLoggable(Level.FINE)) log.fine("Title is " + title); winAttr.title = title; updateWMName(); } protected String getWMName() { if (winAttr.title == null || winAttr.title.trim().equals("")) { return " "; } else { return winAttr.title; } } void updateWMName() { super.updateWMName(); String name = getWMName(); XToolkit.awtLock(); try { if (name == null || name.trim().equals("")) { name = "Java"; } XAtom iconNameAtom = XAtom.get(XAtom.XA_WM_ICON_NAME); iconNameAtom.setProperty(getWindow(), name); XAtom netIconNameAtom = XAtom.get("_NET_WM_ICON_NAME"); netIconNameAtom.setPropertyUTF8(getWindow(), name); } finally { XToolkit.awtUnlock(); } } // NOTE: This method may be called by privileged threads. // DO NOT INVOKE CLIENT CODE ON THIS THREAD! public void handleIconify() { postEvent(new WindowEvent((Window)target, WindowEvent.WINDOW_ICONIFIED)); } // NOTE: This method may be called by privileged threads. // DO NOT INVOKE CLIENT CODE ON THIS THREAD! public void handleDeiconify() { postEvent(new WindowEvent((Window)target, WindowEvent.WINDOW_DEICONIFIED)); } public void handleFocusEvent(XEvent xev) { super.handleFocusEvent(xev); XFocusChangeEvent xfe = xev.get_xfocus(); // If we somehow received focus events forward it instead to proxy // FIXME: Shouldn't we instead check for inferrior? focusLog.finer("Received focus event on shell: " + xfe); // focusProxy.xRequestFocus(); } /*************************************************************************************** * I N S E T S C O D E **************************************************************************************/ /** * Current native insets of the window on the desktop. * When it's noticed the insets might have been changed (like when we * receive a ReparentNotify), the value must be reset to null by the * setNativeInsets(null) call. * * Synchronization: the awtLock is used. This should have been the state * lock instead, but the retrieveNativeInsets() uses the awtLock, and the * later must be always taken before the state lock. * * The field MUST NOT be used directly. Use get/setNativeInsets() instead. */ private Insets nativeInsets = null; /** * Gets the current native insets. * This method may only return null if the fallBackToDefault is false. */ private Insets getNativeInsets(boolean retrieve, boolean fallBackToDefault) { if (isTargetUndecorated() || isEmbedded() || !XWM.isRunning()) { return (Insets)ZERO_INSETS.clone(); } if (getWindow() == XConstants.None) { return getDefaultInsets(); } XToolkit.awtLock(); try { if (nativeInsets == null && retrieve) { retrieveNativeInsets(); } return nativeInsets == null ? (fallBackToDefault ? getDefaultInsets() : null) : (Insets)nativeInsets.clone(); } finally { XToolkit.awtUnlock(); } } /** * Gets the current native insets. */ public Insets getNativeInsets() { return getNativeInsets(true, true); } @Override public Insets getInsets() { Insets insets = getNativeInsets(); insets.top += getMenuBarHeight(); if (insLog.isLoggable(Level.FINEST)) { insLog.log(Level.FINEST, "Get insets returns {0}", new Object[] {insets}); } return insets; } /** * Returns the insets that the window probably will get. */ private Insets getDefaultInsets() { Insets insets = XWM.getWM().getDefaultInsets(); return (Insets)(insets == null ? DEFAULT_INSETS : insets).clone(); } /** * Get rid of insane insets. * The operation is performed in place - the given object gets modified. */ private static Insets sanitize(Insets insets) { //XXX: Perhaps using the marginal values instead of the default would // make more sense? if (insets.top > 64 || insets.top < 0) { insets.top = DEFAULT_INSETS.top; } if (insets.left > 32 || insets.left < 0) { insets.left = DEFAULT_INSETS.left; } if (insets.right > 32 || insets.right < 0) { insets.right = DEFAULT_INSETS.right; } if (insets.bottom > 32 || insets.bottom < 0) { insets.bottom = DEFAULT_INSETS.bottom; } return insets; } private void setNativeInsets(Insets insets) { XToolkit.awtLock(); try { nativeInsets = insets == null ? null : sanitize((Insets)insets.clone()); } finally { XToolkit.awtUnlock(); } } public static final Insets DEFAULT_INSETS = new Insets(25, 5, 5, 5); public static final Insets ZERO_INSETS = new Insets(0, 0, 0, 0); /** * Retrieve the current insets of the window. * * This method must only be called by the getNativeInsets() method. DO NOT * call this method directly. If the insets need to be updated, nullify * them using the setNativeInsets(null) method. */ private void retrieveNativeInsets() { long window = getWindow(); if (XWM.requestWMExtents(window)) { XWM.waitForExtentsUpdateEvent(); } // Some WMs may provide the extents via a property, but do not require // a request to update them. Hence try getting them unconditionally. // We could use the XEvent returned by the waitForExtentsUpdateEvent() // above, though that doesn't seem to make much sense. Insets insets = XWM.getInsetsFromExtents(window); if (insets != null) { setNativeInsets(insets); return; } // The window manager is unable to report any extents, so we need to // calculate them ourselves. Rectangle winRect = XlibUtil.getWindowGeometry( window, XlibWrapper.larg1); if (winRect == null) { // Some error occured return; } // The root window must be got immediately after the previous // getWindowGeometry() call. long root = Native.getWindow(XlibWrapper.larg1); long parent = getNativeParent(); if (parent == XConstants.None || parent == root) { // Non-reparenting WM. Assume the insets are zero. setNativeInsets(ZERO_INSETS); return; } Rectangle parentRect = XlibUtil.getWindowGeometry(parent); if (parentRect == null) { // Some error again return; } long grand_parent = XlibUtil.getParentWindow(parent); if (grand_parent == XConstants.None || grand_parent == root || (winRect.x != 0 || winRect.y != 0 || winRect.width != parentRect.width || winRect.height != parentRect.height)) { // Single-reparenting WM // Either there's no a valid grand-parent, or the direct parent // has greater bounds than the window itself. setNativeInsets(calculateInsets(winRect, parentRect)); } else { // Double-reparenting WM // There's a valid grand-parent. The bounds of the direct // parent are equal to the bounds of the window itself. Rectangle grandParentRect = XlibUtil.getWindowGeometry( grand_parent); if (grandParentRect == null) { // One more error condition. This time we fall back to the // single-reparenting case, though this will produce // ZERO_INSETS actually... setNativeInsets(calculateInsets(winRect, parentRect)); } else { setNativeInsets(calculateInsets(parentRect, grandParentRect)); } } } /** * Calculates the insets for the given interior and exterior rectangles. * Only positive insets are allowed. If the interior rectangle is bigger * than the exterior one, the negative inset values will be ignored, * and zero value used instead. */ private static Insets calculateInsets(Rectangle interior, Rectangle exterior) { return new Insets( Math.max(interior.y, 0), Math.max(interior.x, 0), Math.max(exterior.height - interior.height - interior.y, 0), Math.max(exterior.width - interior.width - interior.x, 0)); } /** * Applies the new insets. */ private void setInsets(Insets insets) { setNativeInsets(insets); WindowDimensions dims = new WindowDimensions(dimensions); dims.setInsets(insets); reportOrAdjust(dims, false); } @Override public void handlePropertyNotify(XEvent xev) { super.handlePropertyNotify(xev); XPropertyEvent ev = xev.get_xproperty(); if (XWM.isExtentsPropertyAtom(ev.get_atom())) { Insets insets = XWM.getInsetsFromProp(getWindow(), XAtom.get(ev.get_atom())); if (insLog.isLoggable(Level.FINE)) { insLog.fine("" + insets); } if (insets != null) { setInsets(insets); } } } // The serial of the last received ReparentNotify event private long reparentNotifySerial = 0; // The number of processed ConfigureNotify events with the same serial // as reparentNotifySerial. private int numOfConfigureNotifyJustAfterReparentNotify = 0; @Override public void handleReparentNotifyEvent(XEvent xev) { super.handleReparentNotifyEvent(xev); XReparentEvent xe = xev.get_xreparent(); if (insLog.isLoggable(Level.FINE)) { insLog.fine(xe.toString()); } if (xe.get_window() != getWindow()) { return; } if (!isParented()) { if (isVisible()) { // Either the WM or the embedder exited XWM.getWM().unshadeKludge(this); if (!isEmbedded()) { XWM.reset(); } setInsets(null); } //else: The window just got hidden } else { // We just got parented: prepare stuff to recalculate the insets, // and to readjust the bounds if needed reparentNotifySerial = xe.get_serial(); numOfConfigureNotifyJustAfterReparentNotify = 0; setNativeInsets(null); needToAdjustBounds(); } } protected boolean isInitialReshape() { return false; } public void handleMoved(Point loc) { ComponentAccessor.setX((Component)target, loc.x); ComponentAccessor.setY((Component)target, loc.y); postEvent(new ComponentEvent(target, ComponentEvent.COMPONENT_MOVED)); } public void revalidate() { XToolkit.executeOnEventHandlerThread(target, new Runnable() { public void run() { target.invalidate(); target.validate(); } }); } boolean gravityBug() { return XWM.configureGravityBuggy(); } // The height of area used to display current active input method int getInputMethodHeight() { return 0; } void updateSizeHints(WindowDimensions dims) { Rectangle rec = dims.getClientRect(); checkShellRect(rec); updateSizeHints(rec.x, rec.y, rec.width, rec.height); } void updateSizeHints() { updateSizeHints(dimensions); } // Coordinates are that of the target // Called only on Toolkit thread private void reshape(WindowDimensions newDimensions, int op) { if (insLog.isLoggable(Level.FINE)) { insLog.fine("Reshaping " + this + " to " + newDimensions + "; op " + operationToString(op)); } XToolkit.awtLock(); try { if (!isVisible()) { Rectangle client = newDimensions.getClientRect(); checkShellRect(client); setShellBounds(client); reportReshape(newDimensions); } else { requestReshape(newDimensions, op); } } finally { XToolkit.awtUnlock(); } } /** * Sets window dimensions and propagates the changes to the target sending * corresponding events if needed. * * MUST be invoked under the AWTLock. */ private void reportReshape(WindowDimensions newDims) { if (insLog.isLoggable(Level.FINE)) { insLog.fine("" + newDims); } final Insets insets = newDims.getInsets(); if (!insets.equals(dimensions.getInsets())) { // Recalculate the minimum size of the client area updateMinSizeHints(insets); } // If the client area size changes, we need to revalidate the target. // This may happen in the XContentWindow.setContentBounds(). If this // does not happen there, we need to dispatch the operation here. final boolean needRevalidate = !dimensions.getClientSize().equals(newDims.getClientSize()); checkIfOnNewScreen(newDims.getBounds()); Point oldLocation = getLocation(); dimensions = newDims; if (!getLocation().equals(oldLocation)) { handleMoved(getLocation()); } if (!reconfigureContentWindow(dimensions) && needRevalidate) { revalidate(); } updateChildrenSizes(); repositionSecurityWarning(); } private void reportOrAdjust(WindowDimensions newDims, boolean handlingConfigureNotify) { if (dimensions.equals(newDims) && !areBoundsAdjusting()) { // If nothing has changed and we're already adjusted, return if (insLog.isLoggable(Level.FINE)) { insLog.fine("Ignored: nothing changed: " + newDims); } return; } if (adjustBounds(newDims, handlingConfigureNotify)) { // We expect another ConfigureNotify return; } reportReshape(newDims); } /** * Requests the system to reshape the window. * * MUST be invoked under the AWTLock. */ private void requestReshape(WindowDimensions newDimensions, int op) { if (insLog.isLoggable(Level.FINE)) { insLog.fine("Request reshape: " + newDimensions + "; op: " + operationToString(op)); } Rectangle shellRect = newDimensions.getClientRect(); if (gravityBug()) { Insets in = newDimensions.getInsets(); shellRect.translate(in.left, in.top); } if ((op & NO_EMBEDDED_CHECK) == 0 && isEmbedded()) { shellRect.setLocation(0, 0); } checkShellRectSize(shellRect); if (!isEmbedded()) { checkShellRectPos(shellRect); } op = op & ~NO_EMBEDDED_CHECK; if (op == SET_LOCATION) { setShellPosition(shellRect); } else if (isResizable()) { if (op == SET_BOUNDS) { setShellBounds(shellRect); } else { setShellSize(shellRect); } } else { XWM.setShellNotResizable(this, newDimensions, shellRect, true); if (op == SET_BOUNDS) { setShellPosition(shellRect); } } } // This method gets overriden in XFramePeer & XDialogPeer. abstract boolean isTargetUndecorated(); /** * @see java.awt.peer.ComponentPeer#setBounds */ public void setBounds(int x, int y, int width, int height, int operation) { WindowDimensions dims = new WindowDimensions(dimensions); switch (operation & (~NO_EMBEDDED_CHECK)) { case SET_LOCATION: // Set location always sets bounds location. However, until the window is mapped we // should use client coordinates dims.setLocation(x, y); break; case SET_SIZE: // Set size sets bounds size. However, until the window is mapped we // should use client coordinates dims.setSize(width, height); break; case SET_CLIENT_SIZE: { // Sets client rect size. Width and height contain insets. // Also update the insets to the latest known value to decrease // (or even eliminate) the bounds adjustment after showing // the window. Insets in = getNativeInsets(); width -= in.left+in.right; height -= in.top+in.bottom; dims.setClientSize(width, height); dims.setInsets(in); break; } case SET_BOUNDS: default: dims.setLocation(x, y); dims.setSize(width, height); break; } reshape(dims, operation); validateSurface(); } /** * Sets the content window bounds. * * @return whether a COMPONENT_RESIZED has been sent */ boolean reconfigureContentWindow(WindowDimensions dims) { if (content == null) { insLog.fine("WARNING: Content window is null"); return false; } return content.setContentBounds(dims); } /** * Indicates if the adjustBounds() needs to be invoked. * Synchronization: state lock. */ private boolean areBoundsAdjusted = false; /** * Forces the system to re-adjust the bounds of the window after it has * been finally adopted by the windowing system. */ private void needToAdjustBounds() { synchronized (getStateLock()) { areBoundsAdjusted = false; } } /** * Indicates if the window bounds has not yet been finally configured by * the X server/window manager. */ public boolean areBoundsAdjusting() { synchronized (getStateLock()) { return !areBoundsAdjusted; } } // XXX: Perhaps the following two might be replaced with Window.isSizeSet, // isLocationSet? Just like we have Window.isPacked... /** * Indicates if the size hints of the window specify a position requested * by the user's code. */ private boolean isUserSpecifiedPositionSet() { return (getHints().get_flags() & (XUtilConstants.USPosition | XUtilConstants.PPosition)) != 0; } /** * Indicates if the size hints of the window specify a position requested * by the user's code. */ private boolean isUserSpecifiedSizeSet() { return (getHints().get_flags() & (XUtilConstants.USSize | XUtilConstants.PSize)) != 0; } /** * Adjust the bounds of the window. * * @return true if an adjustment has taken place. false if the new bounds * are OK. */ private boolean adjustBounds(WindowDimensions newDims, boolean handlingConfigureNotify) { synchronized (getStateLock()) { if (areBoundsAdjusted) { return false; } // We have to adjust bounds upon showing once. // We also may adjust them several times before showing - e.g. // when we receive the PropertyNotify event with the extents. if (handlingConfigureNotify && isVisible()) { insLog.log(Level.FINE, "Final adjustment"); areBoundsAdjusted = true; } } if (isMaximized()) { return false; } int operation = 0; boolean isPacked = AWTAccessor.getWindowAccessor(). isPacked((Window)target); if (!newDims.getClientSize().equals(dimensions.getClientSize()) && isPacked) { // The client area size calculated via pack() needs to be preserved operation = SET_SIZE; } else if (!newDims.getSize().equals(dimensions.getSize()) && isUserSpecifiedSizeSet()) { // The size set via setSize()/setBounds() needs to be preserved operation = SET_SIZE; } if (isUserSpecifiedPositionSet() && !newDims.getLocation().equals(dimensions.getLocation())) { // The user also requested a specific position if (operation == 0) { operation = SET_LOCATION; } else { operation = SET_BOUNDS; } } if (operation == 0) { insLog.fine("No adjustment needed"); return false; } WindowDimensions dims = new WindowDimensions( dimensions.getLocation(), isPacked ? dimensions.getClientSize() : dimensions.getSize(), newDims.getInsets(), isPacked); if (insLog.isLoggable(Level.FINE)) { insLog.fine("Request: " + dims + "; operation=" + operationToString(operation)); } requestReshape(dims, operation); return true; } @Override public void handleConfigureNotifyEvent(XEvent xev) { // Note: we don't call super because the XWindowPeer immediately calls // the checkIfOnNewScreen() assuming the bounds are correct. This // is not always correct for decorated peers. XConfigureEvent xe = xev.get_xconfigure(); // Due to the SubstructureNotifyMask we should process only those // events that belong to us. if (xe.get_window() != getWindow()) { return; } if (insLog.isLoggable(Level.FINE)) { insLog.fine("" + xe); insLog.fine("XWM.isRunning: " + XWM.isRunning()); } // If there's a WM we have to ignore some events if (XWM.isRunning()) { // Ignore any events until after we become visible if (!isVisible()) { insLog.fine("Ignored: Not visible yet"); needToAdjustBounds(); return; } // We have not yet been parented by the WM if (mayBeReparented() && !isTargetUndecorated()) { insLog.fine("Ignored: Not parented yet"); needToAdjustBounds(); return; } // Just after reparenting we can receive a synthetic and/or real // event(s). Since sometimes we need to adjust the bounds of the // frame, we need to process only one of the events. Justification: // 1. Both events carry the same information: one from the X // server, the other from the WM. // 2. The adjustment operation is performed only once upon // processing the first ConfigureNotify event that is not // ignored. // 3. If the first event causes the adjustment to happen, the // second event will be considered as a normal, not ignored // event that may generate Java events and set incorrect // (not-yet-adjusted) bounds to the frame. // So generally, if the adjustment is needed, the first // ConfigureNotify that we should really process is the one caused // by the adjustment operation. So we process only the first // ConfigureNotify event having the same serial that the preceeding // ReparentNotify. // // There's one exception however: we do not ignore the events for // maximized frames. The real ConfigureNotify that we would like to // act upon usually is the third one (it carries the correct // maximized bounds). However, it has the same serial (which is // obvious), and what's worse - we can't reliably determine if the // window manager/X server send us exactly two events before that, // or maybe one of them might be not sent at all - and in this case // we would need to process the second event, not the third. So we // just process everything for a maximized frame. This does produce // absolutely meaningless bounds dancing (with events sent to the // user space), but at least this is backward-compatible and quite // reliable. Should we find a way to detect and process only the // very last of ConfigureNotify events sent with the same serial, // we would like to use this approach instead of the current // processing of the very first event only. if (xe.get_serial() == reparentNotifySerial && !isMaximized()) { if (++numOfConfigureNotifyJustAfterReparentNotify > 1) { insLog.fine("Ignored: serial matches the ReparentNotify"); return; } } } // At this point we can calculate some reliable insets Insets insets = getNativeInsets(); // Note that in some cases this kind of event may be caused by the // changed insets (like if the theme changes, or something). However // it's difficult if at all possible to identify this situation, and // not confuse it with a regular reconfiguration w/o introducing some // serious performance degradation. So we only support changing the // insets: // a. when a reparenting WM exits/gets replaced. // b. when the window gets hidden/shown. // c. if the WM is smart enough to send the PropertyNotify // events with the updated extents. // x, y, width, height hold the coordinates of the whole frame // including the decorations. int x; int y; if (xe.get_send_event() || !isParented()) { // If the event is synthetic or we're not parented, the coordinates // are relative to the root window. x = xe.get_x() - insets.left; y = xe.get_y() - insets.top; } else { // The event is real and we're parented - the coordinates are // relative to the parent Point loc = null; if (XWM.isNoSyntheticConfigureNotifyOnLeftTopResize()) { // there's a bug in the WM, so ask the X server loc = queryXLocation(); } if (loc == null) { // consider the current location unchanged // TODO: this may not be true... perhaps querying X in all cases is worthwhile? // after all: we already do this for Metacity which is quite common, // so we should not worry much about the performance. loc = getLocation(); } x = loc.x; y = loc.y; } int width = xe.get_width() + insets.left + insets.right; int height = xe.get_height() + insets.top + insets.bottom; // Now apply the new bounds WindowDimensions d = new WindowDimensions( new Rectangle(x, y, width, height), insets, false); reportOrAdjust(d, true); } private void checkShellRectSize(Rectangle shellRect) { if (shellRect.width < 0) { shellRect.width = 1; } if (shellRect.height < 0) { shellRect.height = 1; } } private void checkShellRectPos(Rectangle shellRect) { int wm = XWM.getWMID(); if (wm == XWM.MOTIF_WM || wm == XWM.CDE_WM) { if (shellRect.x == 0 && shellRect.y == 0) { shellRect.x = shellRect.y = 1; } } } private void checkShellRect(Rectangle shellRect) { checkShellRectSize(shellRect); checkShellRectPos(shellRect); } public void setShellBounds(Rectangle rec) { if (insLog.isLoggable(Level.FINE)) insLog.fine("Setting shell bounds on " + this + " to " + rec); XToolkit.awtLock(); try { updateSizeHints(rec.x, rec.y, rec.width, rec.height); XlibWrapper.XResizeWindow(XToolkit.getDisplay(), getShell(), rec.width, rec.height); XlibWrapper.XMoveWindow(XToolkit.getDisplay(), getShell(), rec.x, rec.y); } finally { XToolkit.awtUnlock(); } } public void setShellSize(Rectangle rec) { if (insLog.isLoggable(Level.FINE)) insLog.fine("Setting shell size on " + this + " to " + rec); XToolkit.awtLock(); try { updateSizeHints(rec.x, rec.y, rec.width, rec.height); XlibWrapper.XResizeWindow(XToolkit.getDisplay(), getShell(), rec.width, rec.height); } finally { XToolkit.awtUnlock(); } } public void setShellPosition(Rectangle rec) { if (insLog.isLoggable(Level.FINE)) insLog.fine("Setting shell position on " + this + " to " + rec); XToolkit.awtLock(); try { updateSizeHints(rec.x, rec.y, rec.width, rec.height); XlibWrapper.XMoveWindow(XToolkit.getDisplay(), getShell(), rec.x, rec.y); } finally { XToolkit.awtUnlock(); } } void initResizability() { setResizable(winAttr.initialResizability); } public void setResizable(boolean resizable) { int fs = winAttr.functions; if (!isResizable() && resizable) { setNativeInsets(null); winAttr.isResizable = resizable; if ((fs & MWMConstants.MWM_FUNC_ALL) != 0) { fs &= ~(MWMConstants.MWM_FUNC_RESIZE | MWMConstants.MWM_FUNC_MAXIMIZE); } else { fs |= (MWMConstants.MWM_FUNC_RESIZE | MWMConstants.MWM_FUNC_MAXIMIZE); } winAttr.functions = fs; XWM.setShellResizable(this); } else if (isResizable() && !resizable) { setNativeInsets(null); winAttr.isResizable = resizable; if ((fs & MWMConstants.MWM_FUNC_ALL) != 0) { fs |= (MWMConstants.MWM_FUNC_RESIZE | MWMConstants.MWM_FUNC_MAXIMIZE); } else { fs &= ~(MWMConstants.MWM_FUNC_RESIZE | MWMConstants.MWM_FUNC_MAXIMIZE); } winAttr.functions = fs; XWM.setShellNotResizable(this, dimensions, dimensions.getBounds(), false); } } Rectangle getShellBounds() { return dimensions.getClientRect(); } public Rectangle getBounds() { return dimensions.getBounds(); } public Dimension getSize() { return dimensions.getSize(); } public int getX() { return dimensions.getLocation().x; } public int getY() { return dimensions.getLocation().y; } public Point getLocation() { return dimensions.getLocation(); } public int getAbsoluteX() { // NOTE: returning this peer's location which is shell location return dimensions.getScreenBounds().x; } public int getAbsoluteY() { // NOTE: returning this peer's location which is shell location return dimensions.getScreenBounds().y; } public int getWidth() { return getSize().width; } public int getHeight() { return getSize().height; } final public WindowDimensions getDimensions() { return dimensions; } public Point getLocationOnScreen() { XToolkit.awtLock(); try { if (!areBoundsAdjusting()) { return toGlobal(0,0); } else { Point location = target.getLocation(); if (insLog.isLoggable(Level.FINE)) { insLog.log(Level.FINE, "getLocationOnScreen {0} not reparented: {1} ", new Object[] {this, location}); } return location; } } finally { XToolkit.awtUnlock(); } } /*************************************************************************************** * END OF I N S E T S C O D E **************************************************************************************/ protected boolean isEventDisabled(XEvent e) { switch (e.get_type()) { // Do not generate MOVED/RESIZED events since we generate them by ourselves case XConstants.ConfigureNotify: return true; case XConstants.EnterNotify: case XConstants.LeaveNotify: // Disable crossing event on outer borders of Frame so // we receive only one set of cross notifications(first set is from content window) return true; default: return super.isEventDisabled(e); } } int getDecorations() { return winAttr.decorations; } int getFunctions() { return winAttr.functions; } public void setVisible(boolean vis) { log.log(Level.FINER, "Setting {0} to visible {1}", new Object[] {this, Boolean.valueOf(vis)}); if (vis && !isVisible()) { XWM.setShellDecor(this); super.setVisible(vis); if (winAttr.isResizable) { //Fix for 4320050: Minimum size for java.awt.Frame is not being enforced. //We need to update frame's minimum size, not to reset it XWM.removeSizeHints(this, XUtilConstants.PMaxSize); updateMinimumSize(); } } else { super.setVisible(vis); } } protected void suppressWmTakeFocus(boolean doSuppress) { XAtomList protocols = getWMProtocols(); if (doSuppress) { protocols.remove(wm_take_focus); } else { protocols.add(wm_take_focus); } wm_protocols.setAtomListProperty(this, protocols); } public void dispose() { if (content != null) { content.destroy(); } focusProxy.destroy(); if (iconWindow != null) { iconWindow.destroy(); } super.dispose(); } public void handleClientMessage(XEvent xev) { super.handleClientMessage(xev); XClientMessageEvent cl = xev.get_xclient(); if ((wm_protocols != null) && (cl.get_message_type() == wm_protocols.getAtom())) { if (cl.get_data(0) == wm_delete_window.getAtom()) { handleQuit(); } else if (cl.get_data(0) == wm_take_focus.getAtom()) { handleWmTakeFocus(cl); } } } private void handleWmTakeFocus(XClientMessageEvent cl) { focusLog.log(Level.FINE, "WM_TAKE_FOCUS on {0}", new Object[]{this}); requestWindowFocus(cl.get_data(1), true); } /** * Requests focus to this decorated top-level by requesting X input focus * to the shell window. */ protected void requestXFocus(long time, boolean timeProvided) { // We have proxied focus mechanism - instead of shell the focus is held // by "proxy" - invisible mapped window. When we want to set X input focus to // toplevel set it on proxy instead. if (focusProxy == null) { if (focusLog.isLoggable(Level.FINE)) focusLog.warning("Focus proxy is null for " + this); } else { if (focusLog.isLoggable(Level.FINE)) focusLog.fine("Requesting focus to proxy: " + focusProxy); if (timeProvided) { focusProxy.xRequestFocus(time); } else { focusProxy.xRequestFocus(); } } } XFocusProxyWindow getFocusProxy() { return focusProxy; } public void handleQuit() { postEvent(new WindowEvent((Window)target, WindowEvent.WINDOW_CLOSING)); } final void dumpMe() { System.err.println(">>> Peer: " + x + ", " + y + ", " + width + ", " + height); } final void dumpTarget() { int getWidth = ComponentAccessor.getWidth((Component)target); int getHeight = ComponentAccessor.getHeight((Component)target); int getTargetX = ComponentAccessor.getX((Component)target); int getTargetY = ComponentAccessor.getY((Component)target); System.err.println(">>> Target: " + getTargetX + ", " + getTargetY + ", " + getWidth + ", " + getHeight); } final void dumpShell() { dumpWindow("Shell", getShell()); } final void dumpContent() { dumpWindow("Content", getContentWindow()); } final void dumpParent() { long parent = XlibUtil.getParentWindow(getShell()); if (parent != XConstants.None) { dumpWindow("Parent", parent); } else { System.err.println(">>> NO PARENT"); } } final void dumpWindow(String id, long window) { XWindowAttributes pattr = new XWindowAttributes(); try { XToolkit.awtLock(); try { int status = XlibWrapper.XGetWindowAttributes(XToolkit.getDisplay(), window, pattr.pData); } finally { XToolkit.awtUnlock(); } System.err.println(">>>> " + id + ": " + pattr.get_x() + ", " + pattr.get_y() + ", " + pattr.get_width() + ", " + pattr.get_height()); } finally { pattr.dispose(); } } final void dumpAll() { dumpTarget(); dumpMe(); dumpParent(); dumpShell(); dumpContent(); } boolean isMaximized() { return false; } boolean isOverrideRedirect() { return ((XToolkit)Toolkit.getDefaultToolkit()).isOverrideRedirect((Window)target); } public boolean requestWindowFocus(long time, boolean timeProvided) { focusLog.fine("Request for decorated window focus"); // If this is Frame or Dialog we can't assure focus request success - but we still can try // If this is Window and its owner Frame is active we can be sure request succedded. Window focusedWindow = XKeyboardFocusManagerPeer.getCurrentNativeFocusedWindow(); Window activeWindow = XWindowPeer.getDecoratedOwner(focusedWindow); focusLog.log(Level.FINER, "Current window is: active={0}, focused={1}", new Object[]{ Boolean.valueOf(target == activeWindow), Boolean.valueOf(target == focusedWindow)}); XWindowPeer toFocus = this; while (toFocus.nextTransientFor != null) { toFocus = toFocus.nextTransientFor; } if (toFocus == null || !toFocus.focusAllowedFor()) { // This might change when WM will have property to determine focus policy. // Right now, because policy is unknown we can't be sure we succedded return false; } if (this == toFocus) { if (isWMStateNetHidden()) { focusLog.fine("The window is unmapped, so rejecting the request"); return false; } if (target == activeWindow && target != focusedWindow) { // Happens when an owned window is currently focused focusLog.fine("Focus is on child window - transfering it back to the owner"); handleWindowFocusInSync(-1); return true; } Window realNativeFocusedWindow = XWindowPeer.getNativeFocusedWindow(); focusLog.finest("Real native focused window: " + realNativeFocusedWindow + "\nKFM's focused window: " + focusedWindow); // See 6522725, 6613426. if (target == realNativeFocusedWindow) { focusLog.fine("The window is already natively focused."); return true; } } focusLog.fine("Requesting focus to " + (this == toFocus ? "this window" : toFocus)); if (timeProvided) { toFocus.requestXFocus(time); } else { toFocus.requestXFocus(); } return (this == toFocus); } XWindowPeer actualFocusedWindow = null; void setActualFocusedWindow(XWindowPeer actualFocusedWindow) { synchronized(getStateLock()) { this.actualFocusedWindow = actualFocusedWindow; } } boolean requestWindowFocus(XWindowPeer actualFocusedWindow, long time, boolean timeProvided) { setActualFocusedWindow(actualFocusedWindow); return requestWindowFocus(time, timeProvided); } public void handleWindowFocusIn(long serial) { if (null == actualFocusedWindow) { super.handleWindowFocusIn(serial); } else { /* * Fix for 6314575. * If this is a result of clicking on one of the Frame's component * then 'actualFocusedWindow' shouldn't be focused. A decision of focusing * it or not should be made after the appropriate Java mouse event (if any) * is handled by the component where 'actualFocusedWindow' value may be reset. * * The fix is based on the empiric fact consisting in that the component * receives native mouse event nearly at the same time the Frame receives * WM_TAKE_FOCUS (when FocusIn is generated via XSetInputFocus call) but * definetely before the Frame gets FocusIn event (when this method is called). */ postEvent(new InvocationEvent(target, new Runnable() { public void run() { XWindowPeer fw = null; synchronized (getStateLock()) { fw = actualFocusedWindow; actualFocusedWindow = null; if (null == fw || !fw.isVisible() || !fw.isFocusableWindow()) { fw = XDecoratedPeer.this; } } fw.handleWindowFocusIn_Dispatch(); } })); } } public void handleWindowFocusOut(Window oppositeWindow, long serial) { Window actualFocusedWindow = XKeyboardFocusManagerPeer.getCurrentNativeFocusedWindow(); // If the actual focused window is not this decorated window then retain it. if (actualFocusedWindow != null && actualFocusedWindow != target) { Window owner = XWindowPeer.getDecoratedOwner(actualFocusedWindow); if (owner != null && owner == target) { setActualFocusedWindow((XWindowPeer) ComponentAccessor.getPeer(actualFocusedWindow)); } } super.handleWindowFocusOut(oppositeWindow, serial); } private Point queryXLocation() { return XlibUtil.translateCoordinates( getContentWindow(), XlibWrapper.RootWindow(XToolkit.getDisplay(), getScreenNumber()), new Point(0, 0)); } }