1 /*
   2  * Copyright (c) 2003, 2009, 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 
  26 
  27 package sun.awt.X11;
  28 
  29 import java.awt.Frame;
  30 import sun.util.logging.PlatformLogger;
  31 
  32 final class XNETProtocol extends XProtocol implements XStateProtocol, XLayerProtocol
  33 {
  34     private final static PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XNETProtocol");
  35     private final static PlatformLogger iconLog = PlatformLogger.getLogger("sun.awt.X11.icon.XNETProtocol");
  36     private static PlatformLogger stateLog = PlatformLogger.getLogger("sun.awt.X11.states.XNETProtocol");
  37 
  38     /**
  39      * XStateProtocol
  40      */
  41     public boolean supportsState(int state) {
  42         return doStateProtocol() ; // TODO - check for Frame constants
  43     }
  44 
  45     public void setState(XWindowPeer window, int state) {
  46         if (log.isLoggable(PlatformLogger.FINE)) log.fine("Setting state of " + window + " to " + state);
  47         if (window.isShowing()) {
  48             requestState(window, state);
  49         } else {
  50             setInitialState(window, state);
  51         }
  52     }
  53 
  54     private void setInitialState(XWindowPeer window, int state) {
  55         XAtomList old_state = window.getNETWMState();
  56         log.fine("Current state of the window {0} is {1}", window, old_state);
  57         if ((state & Frame.MAXIMIZED_VERT) != 0) {
  58             old_state.add(XA_NET_WM_STATE_MAXIMIZED_VERT);
  59         } else {
  60             old_state.remove(XA_NET_WM_STATE_MAXIMIZED_VERT);
  61         }
  62         if ((state & Frame.MAXIMIZED_HORIZ) != 0) {
  63             old_state.add(XA_NET_WM_STATE_MAXIMIZED_HORZ);
  64         } else {
  65             old_state.remove(XA_NET_WM_STATE_MAXIMIZED_HORZ);
  66         }
  67         log.fine("Setting initial state of the window {0} to {1}", window, old_state);
  68         window.setNETWMState(old_state);
  69     }
  70 
  71     private void requestState(XWindowPeer window, int state) {
  72         /*
  73          * We have to use toggle for maximization because of transitions
  74          * from maximization in one direction only to maximization in the
  75          * other direction only.
  76          */
  77         int old_net_state = getState(window);
  78         int max_changed = (state ^ old_net_state) & (Frame.MAXIMIZED_BOTH);
  79 
  80         XClientMessageEvent req = new XClientMessageEvent();
  81         try {
  82             switch(max_changed) {
  83               case 0:
  84                   return;
  85               case Frame.MAXIMIZED_HORIZ:
  86                   req.set_data(1, XA_NET_WM_STATE_MAXIMIZED_HORZ.getAtom());
  87                   req.set_data(2, 0);
  88                   break;
  89               case Frame.MAXIMIZED_VERT:
  90                   req.set_data(1, XA_NET_WM_STATE_MAXIMIZED_VERT.getAtom());
  91                   req.set_data(2, 0);
  92                   break;
  93               case Frame.MAXIMIZED_BOTH:
  94                   req.set_data(1, XA_NET_WM_STATE_MAXIMIZED_HORZ.getAtom());
  95                   req.set_data(2, XA_NET_WM_STATE_MAXIMIZED_VERT.getAtom());
  96                   break;
  97               default:
  98                   return;
  99             }
 100             if (log.isLoggable(PlatformLogger.FINE)) log.fine("Requesting state on " + window + " for " + state);
 101             req.set_type((int)XConstants.ClientMessage);
 102             req.set_window(window.getWindow());
 103             req.set_message_type(XA_NET_WM_STATE.getAtom());
 104             req.set_format(32);
 105             req.set_data(0, _NET_WM_STATE_TOGGLE);
 106             XToolkit.awtLock();
 107             try {
 108                 XlibWrapper.XSendEvent(XToolkit.getDisplay(),
 109                         XlibWrapper.RootWindow(XToolkit.getDisplay(), window.getScreenNumber()),
 110                         false,
 111                         XConstants.SubstructureRedirectMask | XConstants.SubstructureNotifyMask,
 112                         req.pData);
 113             }
 114             finally {
 115                 XToolkit.awtUnlock();
 116             }
 117         } finally {
 118             req.dispose();
 119         }
 120     }
 121 
 122     public int getState(XWindowPeer window) {
 123         return getStateImpl(window);
 124     }
 125 
 126     /*
 127      * New "NET" WM spec: _NET_WM_STATE/Atom[]
 128      */
 129     int getStateImpl(XWindowPeer window) {
 130         XAtomList net_wm_state = window.getNETWMState();
 131         if (net_wm_state.size() == 0) {
 132             return Frame.NORMAL;
 133         }
 134         int java_state = Frame.NORMAL;
 135         if (net_wm_state.contains(XA_NET_WM_STATE_MAXIMIZED_VERT)) {
 136             java_state |= Frame.MAXIMIZED_VERT;
 137         }
 138         if (net_wm_state.contains(XA_NET_WM_STATE_MAXIMIZED_HORZ)) {
 139             java_state |= Frame.MAXIMIZED_HORIZ;
 140         }
 141         return java_state;
 142     }
 143 
 144     public boolean isStateChange(XPropertyEvent e) {
 145         boolean res = doStateProtocol() && (e.get_atom() == XA_NET_WM_STATE.getAtom()) ;
 146 
 147         if (res) {
 148             // Since state change happened, reset our cached state.  It will be re-read by getState
 149             XWindowPeer wpeer = (XWindowPeer)XToolkit.windowToXWindow(e.get_window());
 150             wpeer.setNETWMState(null);
 151         }
 152         return res;
 153     }
 154 
 155     /*
 156      * Work around for 4775545.
 157      */
 158     public void unshadeKludge(XWindowPeer window) {
 159         XAtomList net_wm_state = window.getNETWMState();
 160         net_wm_state.remove(XA_NET_WM_STATE_SHADED);
 161         window.setNETWMState(net_wm_state);
 162     }
 163 
 164     /**
 165      * XLayerProtocol
 166      */
 167     public boolean supportsLayer(int layer) {
 168         return ((layer == LAYER_ALWAYS_ON_TOP) || (layer == LAYER_NORMAL)) && doLayerProtocol();
 169     }
 170 
 171     public void requestState(XWindow window, XAtom state, boolean isAdd) {
 172         XClientMessageEvent req = new XClientMessageEvent();
 173         try {
 174             req.set_type((int)XConstants.ClientMessage);
 175             req.set_window(window.getWindow());
 176             req.set_message_type(XA_NET_WM_STATE.getAtom());
 177             req.set_format(32);
 178             req.set_data(0, isAdd ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE);
 179             req.set_data(1, state.getAtom());
 180             // Fix for 6735584: req.data[2] must be set to 0 when only one property is changed
 181             req.set_data(2, 0);
 182             log.fine("Setting _NET_STATE atom {0} on {1} for {2}", state, window, Boolean.valueOf(isAdd));
 183             XToolkit.awtLock();
 184             try {
 185                 XlibWrapper.XSendEvent(XToolkit.getDisplay(),
 186                         XlibWrapper.RootWindow(XToolkit.getDisplay(), window.getScreenNumber()),
 187                         false,
 188                         XConstants.SubstructureRedirectMask | XConstants.SubstructureNotifyMask,
 189                         req.pData);
 190             }
 191             finally {
 192                 XToolkit.awtUnlock();
 193             }
 194         } finally {
 195             req.dispose();
 196         }
 197     }
 198 
 199     /**
 200      * Helper function to set/reset one state in NET_WM_STATE
 201      * If window is showing then it uses ClientMessage, otherwise adjusts NET_WM_STATE list
 202      * @param window Window which NET_WM_STATE property is being modified
 203      * @param state State atom to be set/reset
 204      * @param reset Indicates operation, 'set' if false, 'reset' if true
 205      */
 206     private void setStateHelper(XWindowPeer window, XAtom state, boolean set) {
 207         log.finer("Window visibility is: withdrawn={0}, visible={1}, mapped={2} showing={3}",
 208                   Boolean.valueOf(window.isWithdrawn()), Boolean.valueOf(window.isVisible()),
 209                   Boolean.valueOf(window.isMapped()), Boolean.valueOf(window.isShowing()));
 210         if (window.isShowing()) {
 211             requestState(window, state, set);
 212         } else {
 213             XAtomList net_wm_state = window.getNETWMState();
 214             log.finer("Current state on {0} is {1}", window, net_wm_state);
 215             if (!set) {
 216                 net_wm_state.remove(state);
 217             } else {
 218                 net_wm_state.add(state);
 219             }
 220             log.fine("Setting states on {0} to {1}", window, net_wm_state);
 221             window.setNETWMState(net_wm_state);
 222         }
 223         XToolkit.XSync();
 224     }
 225 
 226     public void setLayer(XWindowPeer window, int layer) {
 227         setStateHelper(window, XA_NET_WM_STATE_ABOVE, layer == LAYER_ALWAYS_ON_TOP);
 228     }
 229 
 230     /* New "netwm" spec from www.freedesktop.org */
 231     XAtom XA_UTF8_STRING = XAtom.get("UTF8_STRING");   /* like STRING but encoding is UTF-8 */
 232     XAtom XA_NET_SUPPORTING_WM_CHECK = XAtom.get("_NET_SUPPORTING_WM_CHECK");
 233     XAtom XA_NET_SUPPORTED = XAtom.get("_NET_SUPPORTED");      /* list of protocols (property of root) */
 234     XAtom XA_NET_WM_NAME = XAtom.get("_NET_WM_NAME");  /* window property */
 235     XAtom XA_NET_WM_STATE = XAtom.get("_NET_WM_STATE");/* both window property and request */
 236 
 237 /*
 238  * _NET_WM_STATE is a list of atoms.
 239  * NB: Standard spelling is "HORZ" (yes, without an 'I'), but KDE2
 240  * uses misspelled "HORIZ" (see KDE bug #20229).  This was fixed in
 241  * KDE 2.2.  Under earlier versions of KDE2 horizontal and full
 242  * maximization doesn't work .
 243  */
 244     XAtom XA_NET_WM_STATE_MAXIMIZED_HORZ = XAtom.get("_NET_WM_STATE_MAXIMIZED_HORZ");
 245     XAtom XA_NET_WM_STATE_MAXIMIZED_VERT = XAtom.get("_NET_WM_STATE_MAXIMIZED_VERT");
 246     XAtom XA_NET_WM_STATE_SHADED = XAtom.get("_NET_WM_STATE_SHADED");
 247     XAtom XA_NET_WM_STATE_ABOVE = XAtom.get("_NET_WM_STATE_ABOVE");
 248     XAtom XA_NET_WM_STATE_MODAL = XAtom.get("_NET_WM_STATE_MODAL");
 249     XAtom XA_NET_WM_STATE_FULLSCREEN = XAtom.get("_NET_WM_STATE_FULLSCREEN");
 250     XAtom XA_NET_WM_STATE_BELOW = XAtom.get("_NET_WM_STATE_BELOW");
 251     XAtom XA_NET_WM_STATE_HIDDEN = XAtom.get("_NET_WM_STATE_HIDDEN");
 252     XAtom XA_NET_WM_STATE_SKIP_TASKBAR = XAtom.get("_NET_WM_STATE_SKIP_TASKBAR");
 253     XAtom XA_NET_WM_STATE_SKIP_PAGER = XAtom.get("_NET_WM_STATE_SKIP_PAGER");
 254 
 255     public final XAtom XA_NET_WM_WINDOW_TYPE = XAtom.get("_NET_WM_WINDOW_TYPE");
 256     public final XAtom XA_NET_WM_WINDOW_TYPE_NORMAL = XAtom.get("_NET_WM_WINDOW_TYPE_NORMAL");
 257     public final XAtom XA_NET_WM_WINDOW_TYPE_DIALOG = XAtom.get("_NET_WM_WINDOW_TYPE_DIALOG");
 258     public final XAtom XA_NET_WM_WINDOW_TYPE_UTILITY = XAtom.get("_NET_WM_WINDOW_TYPE_UTILITY");
 259     public final XAtom XA_NET_WM_WINDOW_TYPE_POPUP_MENU = XAtom.get("_NET_WM_WINDOW_TYPE_POPUP_MENU");
 260 
 261     XAtom XA_NET_WM_WINDOW_OPACITY = XAtom.get("_NET_WM_WINDOW_OPACITY");
 262 
 263 /* For _NET_WM_STATE ClientMessage requests */
 264     final static int _NET_WM_STATE_REMOVE      =0; /* remove/unset property */
 265     final static int _NET_WM_STATE_ADD         =1; /* add/set property      */
 266     final static int _NET_WM_STATE_TOGGLE      =2; /* toggle property       */
 267 
 268     boolean supportChecked = false;
 269     long NetWindow = 0;
 270     void detect() {
 271         if (supportChecked) {
 272             // TODO: How about detecting WM-restart or exit?
 273             return;
 274         }
 275         NetWindow = checkAnchor(XA_NET_SUPPORTING_WM_CHECK, XAtom.XA_WINDOW);
 276         supportChecked = true;
 277         if (log.isLoggable(PlatformLogger.FINE)) log.fine("### " + this + " is active: " + (NetWindow != 0));
 278     }
 279 
 280     boolean active() {
 281         detect();
 282         return NetWindow != 0;
 283     }
 284 
 285     boolean doStateProtocol() {
 286         boolean res = active() && checkProtocol(XA_NET_SUPPORTED, XA_NET_WM_STATE);
 287         stateLog.finer("doStateProtocol() returns " + res);
 288         return res;
 289     }
 290 
 291     boolean doLayerProtocol() {
 292         boolean res = active() && checkProtocol(XA_NET_SUPPORTED, XA_NET_WM_STATE_ABOVE);
 293         return res;
 294     }
 295 
 296     boolean doModalityProtocol() {
 297         boolean res = active() && checkProtocol(XA_NET_SUPPORTED, XA_NET_WM_STATE_MODAL);
 298         return res;
 299     }
 300 
 301     boolean doOpacityProtocol() {
 302         boolean res = active() && checkProtocol(XA_NET_SUPPORTED, XA_NET_WM_WINDOW_OPACITY);
 303         return res;
 304     }
 305 
 306     boolean isWMName(String name) {
 307         if (!active()) {
 308             return false;
 309         }
 310         String net_wm_name_string = getWMName();
 311         if (net_wm_name_string == null) {
 312             return false;
 313         }
 314         if (log.isLoggable(PlatformLogger.FINE)) log.fine("### WM_NAME = " + net_wm_name_string);
 315         return net_wm_name_string.startsWith(name);
 316     }
 317 
 318     String net_wm_name_cache;
 319     public String getWMName() {
 320         if (!active()) {
 321             return null;
 322         }
 323 
 324         if (net_wm_name_cache != null) {
 325             return net_wm_name_cache;
 326         }
 327 
 328         /*
 329          * Check both UTF8_STRING and STRING.  We only call this function
 330          * with ASCII names and UTF8 preserves ASCII bit-wise.  wm-spec
 331          * mandates UTF8_STRING for _NET_WM_NAME but at least sawfish-1.0
 332          * still uses STRING.  (mmm, moving targets...).
 333          */
 334         String charSet = "UTF8";
 335         byte[] net_wm_name = XA_NET_WM_NAME.getByteArrayProperty(NetWindow, XA_UTF8_STRING.getAtom());
 336         if (net_wm_name == null) {
 337             net_wm_name = XA_NET_WM_NAME.getByteArrayProperty(NetWindow, XAtom.XA_STRING);
 338             charSet = "ASCII";
 339         }
 340 
 341         if (net_wm_name == null) {
 342             return null;
 343         }
 344         try {
 345             net_wm_name_cache = new String(net_wm_name, charSet);
 346             return net_wm_name_cache;
 347         } catch (java.io.UnsupportedEncodingException uex) {
 348             return null;
 349         }
 350     }
 351 
 352     /**
 353      * Sets _NET_WM_ICON property on the window using the List of XIconInfo
 354      * If icons is null or empty list, removes _NET_WM_ICON property
 355      */
 356     public void setWMIcons(XWindowPeer window, java.util.List<XIconInfo> icons) {
 357         if (window == null) return;
 358 
 359         XAtom iconsAtom = XAtom.get("_NET_WM_ICON");
 360         if (icons == null) {
 361             iconsAtom.DeleteProperty(window);
 362             return;
 363         }
 364 
 365         int length = 0;
 366         for (XIconInfo ii : icons) {
 367             length += ii.getRawLength();
 368         }
 369         int cardinalSize = (XlibWrapper.dataModel == 32) ? 4 : 8;
 370         int bufferSize = length * cardinalSize;
 371 
 372         if (bufferSize != 0) {
 373             long buffer = XlibWrapper.unsafe.allocateMemory(bufferSize);
 374             try {
 375                 long ptr = buffer;
 376                 for (XIconInfo ii : icons) {
 377                     int size = ii.getRawLength() * cardinalSize;
 378                     if (XlibWrapper.dataModel == 32) {
 379                         XlibWrapper.copyIntArray(ptr, ii.getIntData(), size);
 380                     } else {
 381                         XlibWrapper.copyLongArray(ptr, ii.getLongData(), size);
 382                     }
 383                     ptr += size;
 384                 }
 385                 iconsAtom.setAtomData(window.getWindow(), XAtom.XA_CARDINAL, buffer, bufferSize/Native.getCard32Size());
 386             } finally {
 387                 XlibWrapper.unsafe.freeMemory(buffer);
 388             }
 389         } else {
 390             iconsAtom.DeleteProperty(window);
 391         }
 392     }
 393 
 394     public boolean isWMStateNetHidden(XWindowPeer window) {
 395         if (!doStateProtocol()) {
 396             return false;
 397         }
 398         XAtomList state = window.getNETWMState();
 399         return (state != null && state.size() != 0 && state.contains(XA_NET_WM_STATE_HIDDEN));
 400     }
 401 }