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