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
27package sun.awt.X11;
28
29import java.awt.Frame;
30
31import sun.awt.IconInfo;
32import sun.util.logging.PlatformLogger;
33
34final class XNETProtocol extends XProtocol implements XStateProtocol, XLayerProtocol
35{
36    private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XNETProtocol");
37    private static final 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 set 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_ACTIVE_WINDOW = XAtom.get("_NET_ACTIVE_WINDOW");
253    XAtom XA_NET_WM_NAME = XAtom.get("_NET_WM_NAME");  /* window property */
254    XAtom XA_NET_WM_STATE = XAtom.get("_NET_WM_STATE");/* both window property and request */
255
256/*
257 * _NET_WM_STATE is a list of atoms.
258 * NB: Standard spelling is "HORZ" (yes, without an 'I'), but KDE2
259 * uses misspelled "HORIZ" (see KDE bug #20229).  This was fixed in
260 * KDE 2.2.  Under earlier versions of KDE2 horizontal and full
261 * maximization doesn't work .
262 */
263    XAtom XA_NET_WM_STATE_MAXIMIZED_HORZ = XAtom.get("_NET_WM_STATE_MAXIMIZED_HORZ");
264    XAtom XA_NET_WM_STATE_MAXIMIZED_VERT = XAtom.get("_NET_WM_STATE_MAXIMIZED_VERT");
265    XAtom XA_NET_WM_STATE_SHADED = XAtom.get("_NET_WM_STATE_SHADED");
266    XAtom XA_NET_WM_STATE_ABOVE = XAtom.get("_NET_WM_STATE_ABOVE");
267    XAtom XA_NET_WM_STATE_MODAL = XAtom.get("_NET_WM_STATE_MODAL");
268    XAtom XA_NET_WM_STATE_FULLSCREEN = XAtom.get("_NET_WM_STATE_FULLSCREEN");
269    XAtom XA_NET_WM_STATE_BELOW = XAtom.get("_NET_WM_STATE_BELOW");
270    XAtom XA_NET_WM_STATE_HIDDEN = XAtom.get("_NET_WM_STATE_HIDDEN");
271    XAtom XA_NET_WM_STATE_SKIP_TASKBAR = XAtom.get("_NET_WM_STATE_SKIP_TASKBAR");
272    XAtom XA_NET_WM_STATE_SKIP_PAGER = XAtom.get("_NET_WM_STATE_SKIP_PAGER");
273
274    public final XAtom XA_NET_WM_WINDOW_TYPE = XAtom.get("_NET_WM_WINDOW_TYPE");
275    public final XAtom XA_NET_WM_WINDOW_TYPE_NORMAL = XAtom.get("_NET_WM_WINDOW_TYPE_NORMAL");
276    public final XAtom XA_NET_WM_WINDOW_TYPE_DIALOG = XAtom.get("_NET_WM_WINDOW_TYPE_DIALOG");
277    public final XAtom XA_NET_WM_WINDOW_TYPE_UTILITY = XAtom.get("_NET_WM_WINDOW_TYPE_UTILITY");
278    public final XAtom XA_NET_WM_WINDOW_TYPE_POPUP_MENU = XAtom.get("_NET_WM_WINDOW_TYPE_POPUP_MENU");
279
280    XAtom XA_NET_WM_WINDOW_OPACITY = XAtom.get("_NET_WM_WINDOW_OPACITY");
281
282/* For _NET_WM_STATE ClientMessage requests */
283    static final int _NET_WM_STATE_REMOVE      =0; /* remove/unset property */
284    static final int _NET_WM_STATE_ADD         =1; /* add/set property      */
285    static final int _NET_WM_STATE_TOGGLE      =2; /* toggle property       */
286
287    boolean supportChecked = false;
288    long NetWindow = 0;
289    void detect() {
290        if (supportChecked) {
291            // TODO: How about detecting WM-restart or exit?
292            return;
293        }
294        NetWindow = checkAnchor(XA_NET_SUPPORTING_WM_CHECK, XAtom.XA_WINDOW);
295        supportChecked = true;
296        if (log.isLoggable(PlatformLogger.Level.FINE)) {
297            log.fine("### " + this + " is active: " + (NetWindow != 0));
298        }
299    }
300
301    boolean active() {
302        detect();
303        return NetWindow != 0;
304    }
305
306    boolean doStateProtocol() {
307        boolean res = active() && checkProtocol(XA_NET_SUPPORTED, XA_NET_WM_STATE);
308        if (stateLog.isLoggable(PlatformLogger.Level.FINER)) {
309            stateLog.finer("doStateProtocol() returns " + res);
310        }
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    public void setActiveWindow(long window) {
330        if (!active() || !checkProtocol(XA_NET_SUPPORTED, XA_NET_ACTIVE_WINDOW)) {
331            return;
332        }
333
334        XClientMessageEvent msg = new XClientMessageEvent();
335        msg.zero();
336        msg.set_type(XConstants.ClientMessage);
337        msg.set_message_type(XA_NET_ACTIVE_WINDOW.getAtom());
338        msg.set_display(XToolkit.getDisplay());
339        msg.set_window(window);
340        msg.set_format(32);
341        msg.set_data(0, 1);
342        msg.set_data(1, XToolkit.getCurrentServerTime());
343        msg.set_data(2, 0);
344
345        XToolkit.awtLock();
346        try {
347            XlibWrapper.XSendEvent(XToolkit.getDisplay(), XToolkit.getDefaultRootWindow(), false,
348                    XConstants.SubstructureRedirectMask | XConstants.SubstructureNotifyMask, msg.getPData());
349        } finally {
350            XToolkit.awtUnlock();
351            msg.dispose();
352        }
353    }
354
355    boolean isWMName(String name) {
356        if (!active()) {
357            return false;
358        }
359        String net_wm_name_string = getWMName();
360        if (net_wm_name_string == null) {
361            return false;
362        }
363        if (log.isLoggable(PlatformLogger.Level.FINE)) {
364            log.fine("### WM_NAME = " + net_wm_name_string);
365        }
366        return net_wm_name_string.startsWith(name);
367    }
368
369    String net_wm_name_cache;
370    public String getWMName() {
371        if (!active()) {
372            return null;
373        }
374
375        if (net_wm_name_cache != null) {
376            return net_wm_name_cache;
377        }
378
379        /*
380         * Check both UTF8_STRING and STRING.  We only call this function
381         * with ASCII names and UTF8 preserves ASCII bit-wise.  wm-spec
382         * mandates UTF8_STRING for _NET_WM_NAME but at least sawfish-1.0
383         * still uses STRING.  (mmm, moving targets...).
384         */
385        String charSet = "UTF8";
386        byte[] net_wm_name = XA_NET_WM_NAME.getByteArrayProperty(NetWindow, XA_UTF8_STRING.getAtom());
387        if (net_wm_name == null) {
388            net_wm_name = XA_NET_WM_NAME.getByteArrayProperty(NetWindow, XAtom.XA_STRING);
389            charSet = "ASCII";
390        }
391
392        if (net_wm_name == null) {
393            return null;
394        }
395        try {
396            net_wm_name_cache = new String(net_wm_name, charSet);
397            return net_wm_name_cache;
398        } catch (java.io.UnsupportedEncodingException uex) {
399            return null;
400        }
401    }
402
403    /**
404     * Sets _NET_WM_ICON property on the window using the List of IconInfo
405     * If icons is null or empty list, removes _NET_WM_ICON property
406     */
407    public void setWMIcons(XWindowPeer window, java.util.List<IconInfo> icons) {
408        if (window == null) return;
409
410        XAtom iconsAtom = XAtom.get("_NET_WM_ICON");
411        if (icons == null) {
412            iconsAtom.DeleteProperty(window);
413            return;
414        }
415
416        int length = 0;
417        for (IconInfo ii : icons) {
418            length += ii.getRawLength();
419        }
420        int cardinalSize = (XlibWrapper.dataModel == 32) ? 4 : 8;
421        int bufferSize = length * cardinalSize;
422
423        if (bufferSize != 0) {
424            long buffer = XlibWrapper.unsafe.allocateMemory(bufferSize);
425            try {
426                long ptr = buffer;
427                for (IconInfo ii : icons) {
428                    int size = ii.getRawLength() * cardinalSize;
429                    if (XlibWrapper.dataModel == 32) {
430                        XlibWrapper.copyIntArray(ptr, ii.getIntData(), size);
431                    } else {
432                        XlibWrapper.copyLongArray(ptr, ii.getLongData(), size);
433                    }
434                    ptr += size;
435                }
436                iconsAtom.setAtomData(window.getWindow(), XAtom.XA_CARDINAL, buffer, bufferSize/Native.getCard32Size());
437            } finally {
438                XlibWrapper.unsafe.freeMemory(buffer);
439            }
440        } else {
441            iconsAtom.DeleteProperty(window);
442        }
443    }
444
445    public boolean isWMStateNetHidden(XWindowPeer window) {
446        if (!doStateProtocol()) {
447            return false;
448        }
449        XAtomList state = window.getNETWMState();
450        return (state != null && state.size() != 0 && state.contains(XA_NET_WM_STATE_HIDDEN));
451    }
452}
453