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