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