1 /*
2  * Copyright (c) 2003, 2020, 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 package sun.awt.X11;
26 
27 import java.awt.AlphaComposite;
28 import java.awt.Dimension;
29 import java.awt.Graphics;
30 import java.awt.Graphics2D;
31 import java.awt.GraphicsConfiguration;
32 import java.awt.Image;
33 import java.awt.Rectangle;
34 import java.awt.SystemColor;
35 import java.awt.image.BufferedImage;
36 import java.awt.image.ColorModel;
37 import java.awt.image.DataBuffer;
38 import java.awt.image.DataBufferByte;
39 import java.awt.image.DataBufferInt;
40 import java.awt.image.DataBufferUShort;
41 import java.awt.image.ImageObserver;
42 import java.awt.image.WritableRaster;
43 
44 import sun.awt.IconInfo;
45 import sun.awt.image.ImageRepresentation;
46 import sun.awt.image.ToolkitImage;
47 import sun.util.logging.PlatformLogger;
48 
49 public class XIconWindow extends XBaseWindow {
50     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XIconWindow");
51     XDecoratedPeer parent;
52     Dimension size;
53     long iconPixmap = 0;
54     long iconMask = 0;
55     int iconWidth = 0;
56     int iconHeight = 0;
XIconWindow(XDecoratedPeer parent)57     XIconWindow(XDecoratedPeer parent) {
58         super(new XCreateWindowParams(new Object[] {
59             PARENT, parent,
60             DELAYED, Boolean.TRUE}));
61     }
62 
instantPreInit(XCreateWindowParams params)63     void instantPreInit(XCreateWindowParams params) {
64         super.instantPreInit(params);
65         this.parent = (XDecoratedPeer)params.get(PARENT);
66     }
67 
68     /**
69      * @return array of XIconsSize structures, caller must free this array after use.
70      */
getIconSizes()71     private XIconSize[] getIconSizes() {
72         XToolkit.awtLock();
73         try {
74             AwtGraphicsConfigData adata = parent.getGraphicsConfigurationData();
75             final long screen = adata.get_awt_visInfo().get_screen();
76             final long display = XToolkit.getDisplay();
77 
78             if (log.isLoggable(PlatformLogger.Level.FINEST)) {
79                 log.finest(adata.toString());
80             }
81 
82             long status =
83                 XlibWrapper.XGetIconSizes(display, XToolkit.getDefaultRootWindow(),
84                                           XlibWrapper.larg1, XlibWrapper.iarg1);
85             if (status == 0) {
86                 return null;
87             }
88             int count = Native.getInt(XlibWrapper.iarg1);
89             long sizes_ptr = Native.getLong(XlibWrapper.larg1); // XIconSize*
90             if (log.isLoggable(PlatformLogger.Level.FINEST)) {
91                 log.finest("count = {1}, sizes_ptr = {0}", Long.valueOf(sizes_ptr), Integer.valueOf(count));
92             }
93             XIconSize[] res = new XIconSize[count];
94             for (int i = 0; i < count; i++, sizes_ptr += XIconSize.getSize()) {
95                 res[i] = new XIconSize(sizes_ptr);
96                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
97                     log.finest("sizes_ptr[{1}] = {0}", res[i], Integer.valueOf(i));
98                 }
99             }
100             return res;
101         } finally {
102             XToolkit.awtUnlock();
103         }
104     }
105 
calcIconSize(int widthHint, int heightHint)106     private Dimension calcIconSize(int widthHint, int heightHint) {
107         if (XWM.getWMID() == XWM.ICE_WM) {
108             // ICE_WM has a bug - it only displays icons of the size
109             // 16x16, while reporting 32x32 in its size list
110             log.finest("Returning ICE_WM icon size: 16x16");
111             return new Dimension(16, 16);
112         }
113 
114         XIconSize[] sizeList = getIconSizes();
115         if (log.isLoggable(PlatformLogger.Level.FINEST)) {
116             log.finest("Icon sizes: {0}", (Object[]) sizeList);
117         }
118         if (sizeList == null) {
119             // No icon sizes so we simply fall back to 16x16
120             return new Dimension(16, 16);
121         }
122         boolean found = false;
123         int dist = 0xffffffff, newDist, diff = 0, closestHeight, closestWidth;
124         int saveWidth = 0, saveHeight = 0;
125         for (int i = 0; i < sizeList.length; i++) {
126             if (widthHint >= sizeList[i].get_min_width() &&
127                 widthHint <= sizeList[i].get_max_width() &&
128                 heightHint >= sizeList[i].get_min_height() &&
129                 heightHint <= sizeList[i].get_max_height()) {
130                 found = true;
131                 if ((((widthHint-sizeList[i].get_min_width())
132                       % sizeList[i].get_width_inc()) == 0) &&
133                     (((heightHint-sizeList[i].get_min_height())
134                       % sizeList[i].get_height_inc()) ==0)) {
135                     /* Found an exact match */
136                     saveWidth = widthHint;
137                     saveHeight = heightHint;
138                     dist = 0;
139                     break;
140                 }
141                 diff = widthHint - sizeList[i].get_min_width();
142                 if (diff == 0) {
143                     closestWidth = widthHint;
144                 } else {
145                     diff = diff%sizeList[i].get_width_inc();
146                     closestWidth = widthHint - diff;
147                 }
148                 diff = heightHint - sizeList[i].get_min_height();
149                 if (diff == 0) {
150                     closestHeight = heightHint;
151                 } else {
152                     diff = diff%sizeList[i].get_height_inc();
153                     closestHeight = heightHint - diff;
154                 }
155                 newDist = closestWidth*closestWidth +
156                     closestHeight*closestHeight;
157                 if (dist > newDist) {
158                     saveWidth = closestWidth;
159                     saveHeight = closestHeight;
160                     dist = newDist;
161                 }
162             }
163         }
164         if (log.isLoggable(PlatformLogger.Level.FINEST)) {
165             log.finest("found=" + found);
166         }
167         if (!found) {
168             if (log.isLoggable(PlatformLogger.Level.FINEST)) {
169                 log.finest("widthHint=" + widthHint + ", heightHint=" + heightHint
170                            + ", saveWidth=" + saveWidth + ", saveHeight=" + saveHeight
171                            + ", max_width=" + sizeList[0].get_max_width()
172                            + ", max_height=" + sizeList[0].get_max_height()
173                            + ", min_width=" + sizeList[0].get_min_width()
174                            + ", min_height=" + sizeList[0].get_min_height());
175             }
176 
177             if (widthHint  > sizeList[0].get_max_width() ||
178                 heightHint > sizeList[0].get_max_height())
179             {
180                 // Icon image too big
181                 /* determine which way to scale */
182                 int wdiff = widthHint - sizeList[0].get_max_width();
183                 int hdiff = heightHint - sizeList[0].get_max_height();
184                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
185                     log.finest("wdiff=" + wdiff + ", hdiff=" + hdiff);
186                 }
187                 if (wdiff >= hdiff) { /* need to scale width more  */
188                     saveWidth = sizeList[0].get_max_width();
189                     saveHeight =
190                         (int)(((double)sizeList[0].get_max_width()/widthHint) * heightHint);
191                 } else {
192                     saveWidth =
193                         (int)(((double)sizeList[0].get_max_height()/heightHint) * widthHint);
194                     saveHeight = sizeList[0].get_max_height();
195                 }
196             } else if (widthHint  < sizeList[0].get_min_width() ||
197                        heightHint < sizeList[0].get_min_height())
198             {
199                 // Icon image too small
200                 saveWidth = (sizeList[0].get_min_width()+sizeList[0].get_max_width())/2;
201                 saveHeight = (sizeList[0].get_min_height()+sizeList[0].get_max_height())/2;
202             } else {
203                 // Icon image fits within right size
204                 saveWidth = widthHint;
205                 saveHeight = widthHint;
206             }
207         }
208 
209         XToolkit.awtLock();
210         try {
211             XlibWrapper.XFree(sizeList[0].pData);
212         } finally {
213             XToolkit.awtUnlock();
214         }
215 
216         if (log.isLoggable(PlatformLogger.Level.FINEST)) {
217             log.finest("return " + saveWidth + "x" + saveHeight);
218         }
219         return new Dimension(saveWidth, saveHeight);
220     }
221 
222     /**
223      * @return preffered icon size calculated from specific icon
224      */
getIconSize(int widthHint, int heightHint)225     Dimension getIconSize(int widthHint, int heightHint) {
226         if (size == null) {
227             size = calcIconSize(widthHint, heightHint);
228         }
229         return size;
230     }
231 
232    /**
233     * This function replaces iconPixmap handle with new image
234     * It does not replace window's hints, so it should be
235     * called only from setIconImage()
236     */
replaceImage(Image img)237    void replaceImage(Image img)
238     {
239         if (parent == null) {
240             return;
241         }
242         //Prepare image
243         //create new buffered image of desired size
244         //in current window's color model
245         BufferedImage bi = null;
246         if (img != null && iconWidth != 0 && iconHeight != 0) {
247             GraphicsConfiguration defaultGC = parent.getGraphicsConfiguration().getDevice().getDefaultConfiguration();
248             ColorModel model = defaultGC.getColorModel();
249             WritableRaster raster = model.createCompatibleWritableRaster(iconWidth, iconHeight);
250             bi = new BufferedImage(model, raster, model.isAlphaPremultiplied(), null);
251             Graphics g = bi.getGraphics();
252             try {
253                 //We need to draw image on SystemColors.window
254                 //for using as iconWindow's background
255                 g.setColor(SystemColor.window);
256                 g.fillRect(0, 0, iconWidth, iconHeight);
257                 if (g instanceof Graphics2D) {
258                     ((Graphics2D)g).setComposite(AlphaComposite.Src);
259                 }
260                 g.drawImage(img, 0, 0, iconWidth, iconHeight, null);
261             } finally {
262                 g.dispose();
263             }
264         }
265         //create pixmap
266         XToolkit.awtLock();
267         try {
268             if (iconPixmap != 0) {
269                 XlibWrapper.XFreePixmap(XToolkit.getDisplay(), iconPixmap);
270                 iconPixmap = 0;
271                 log.finest("Freed previous pixmap");
272             }
273             if (bi == null || iconWidth == 0 || iconHeight == 0) {
274                 return;  //The iconPixmap is 0 now, we have done everything
275             }
276             AwtGraphicsConfigData adata = parent.getGraphicsConfigurationData();
277             awtImageData awtImage = adata.get_awtImage(0);
278             XVisualInfo visInfo = adata.get_awt_visInfo();
279             iconPixmap = XlibWrapper.XCreatePixmap(XToolkit.getDisplay(),
280                                                    XlibWrapper.RootWindow(XToolkit.getDisplay(), visInfo.get_screen()),
281                                                    iconWidth,
282                                                    iconHeight,
283                                                    awtImage.get_Depth()
284                                                    );
285             if (iconPixmap == 0) {
286                 log.finest("Can't create new pixmap for icon");
287                 return; //Can't do nothing
288             }
289             //Transform image data
290             long bytes = 0;
291             DataBuffer srcBuf = bi.getData().getDataBuffer();
292             if (srcBuf instanceof DataBufferByte) {
293                 byte[] buf = ((DataBufferByte)srcBuf).getData();
294                 ColorData cdata = adata.get_color_data(0);
295                 int num_colors = cdata.get_awt_numICMcolors();
296                 for (int i = 0; i < buf.length; i++) {
297                     int b = Byte.toUnsignedInt(buf[i]);
298                     buf[i] = (b >= num_colors) ?
299                         0 : cdata.get_awt_icmLUT2Colors(b);
300                 }
301                 bytes = Native.toData(buf);
302             } else if (srcBuf instanceof DataBufferInt) {
303                 bytes = Native.toData(((DataBufferInt)srcBuf).getData());
304             } else if (srcBuf instanceof DataBufferUShort) {
305                 bytes = Native.toData(((DataBufferUShort)srcBuf).getData());
306             } else {
307                 throw new IllegalArgumentException("Unknown data buffer: " + srcBuf);
308             }
309             int bpp = awtImage.get_wsImageFormat().get_bits_per_pixel();
310             int slp =awtImage.get_wsImageFormat().get_scanline_pad();
311             int bpsl = paddedwidth(iconWidth*bpp, slp) >> 3;
312             if (((bpsl << 3) / bpp) < iconWidth) {
313                 log.finest("Image format doesn't fit to icon width");
314                 return;
315             }
316             long dst = XlibWrapper.XCreateImage(XToolkit.getDisplay(),
317                                                 visInfo.get_visual(),
318                                                 awtImage.get_Depth(),
319                                                 XConstants.ZPixmap,
320                                                 0,
321                                                 bytes,
322                                                 iconWidth,
323                                                 iconHeight,
324                                                 32,
325                                                 bpsl);
326             if (dst == 0) {
327                 log.finest("Can't create XImage for icon");
328                 XlibWrapper.XFreePixmap(XToolkit.getDisplay(), iconPixmap);
329                 iconPixmap = 0;
330                 return;
331             } else {
332                 log.finest("Created XImage for icon");
333             }
334             long gc = XlibWrapper.XCreateGC(XToolkit.getDisplay(), iconPixmap, 0, 0);
335             if (gc == 0) {
336                 log.finest("Can't create GC for pixmap");
337                 XlibWrapper.XFreePixmap(XToolkit.getDisplay(), iconPixmap);
338                 iconPixmap = 0;
339                 return;
340             } else {
341                 log.finest("Created GC for pixmap");
342             }
343             try {
344                 XlibWrapper.XPutImage(XToolkit.getDisplay(), iconPixmap, gc,
345                                       dst, 0, 0, 0, 0, iconWidth, iconHeight);
346             } finally {
347                 XlibWrapper.XFreeGC(XToolkit.getDisplay(), gc);
348             }
349         } finally {
350             XToolkit.awtUnlock();
351         }
352     }
353 
354    /**
355     * This function replaces iconPixmap handle with new image
356     * It does not replace window's hints, so it should be
357     * called only from setIconImage()
358     */
replaceMask(Image img)359     void replaceMask(Image img) {
360         if (parent == null) {
361             return;
362         }
363         //Prepare image
364         BufferedImage bi = null;
365         if (img != null && iconWidth != 0 && iconHeight != 0) {
366             bi = new BufferedImage(iconWidth, iconHeight, BufferedImage.TYPE_INT_ARGB);
367             Graphics g = bi.getGraphics();
368             try {
369                 g.drawImage(img, 0, 0, iconWidth, iconHeight, null);
370             } finally {
371                 g.dispose();
372             }
373         }
374         //create mask
375         XToolkit.awtLock();
376         try {
377             if (iconMask != 0) {
378                 XlibWrapper.XFreePixmap(XToolkit.getDisplay(), iconMask);
379                 iconMask = 0;
380                 log.finest("Freed previous mask");
381             }
382             if (bi == null || iconWidth == 0 || iconHeight == 0) {
383                 return;  //The iconMask is 0 now, we have done everything
384             }
385             AwtGraphicsConfigData adata = parent.getGraphicsConfigurationData();
386             awtImageData awtImage = adata.get_awtImage(0);
387             XVisualInfo visInfo = adata.get_awt_visInfo();
388             ColorModel cm = bi.getColorModel();
389             DataBuffer srcBuf = bi.getRaster().getDataBuffer();
390             int sidx = 0;//index of source element
391             int bpl = (iconWidth + 7) >> 3;//bytes per line
392             byte[] destBuf = new byte[bpl * iconHeight];
393             int didx = 0;//index of destination element
394             for (int i = 0; i < iconHeight; i++) {
395                 int dbit = 0;//index of current bit
396                 int cv = 0;
397                 for (int j = 0; j < iconWidth; j++) {
398                     if (cm.getAlpha(srcBuf.getElem(sidx)) != 0 ) {
399                         cv = cv + (1 << dbit);
400                     }
401                     dbit++;
402                     if (dbit == 8) {
403                         destBuf[didx] = (byte)cv;
404                         cv = 0;
405                         dbit = 0;
406                         didx++;
407                     }
408                     sidx++;
409                 }
410             }
411             iconMask = XlibWrapper.XCreateBitmapFromData(XToolkit.getDisplay(),
412                 XlibWrapper.RootWindow(XToolkit.getDisplay(), visInfo.get_screen()),
413                 Native.toData(destBuf),
414                 iconWidth, iconHeight);
415         } finally {
416             XToolkit.awtUnlock();
417         }
418     }
419 
420     /**
421      * Sets icon image by selecting one of the images from the list.
422      * The selected image is the one having the best matching size.
423      */
setIconImages(java.util.List<IconInfo> icons)424     void setIconImages(java.util.List<IconInfo> icons) {
425         if (icons == null || icons.size() == 0) return;
426 
427         int minDiff = Integer.MAX_VALUE;
428         Image min = null;
429         for (IconInfo iconInfo : icons) {
430             if (iconInfo.isValid()) {
431                 Image image = iconInfo.getImage();
432                 Dimension dim = calcIconSize(image.getWidth(null), image.getHeight(null));
433                 int widthDiff = Math.abs(dim.width - image.getWidth(null));
434                 int heightDiff = Math.abs(image.getHeight(null) - dim.height);
435 
436                 // "=" below allows to select the best matching icon
437                 if (minDiff >= (widthDiff + heightDiff)) {
438                     minDiff = (widthDiff + heightDiff);
439                     min = image;
440                 }
441             }
442         }
443         if (min != null) {
444             if (log.isLoggable(PlatformLogger.Level.FINER)) {
445                 log.finer("Icon: {0}x{1}", min.getWidth(null), min.getHeight(null));
446             }
447             setIconImage(min);
448         }
449     }
450 
setIconImage(Image img)451     void setIconImage(Image img) {
452         if (img == null) {
453             //if image is null, reset to default image
454             replaceImage(null);
455             replaceMask(null);
456         } else {
457             //get image size
458             int width;
459             int height;
460             if (img instanceof ToolkitImage) {
461                 ImageRepresentation ir = ((ToolkitImage)img).getImageRep();
462                 ir.reconstruct(ImageObserver.ALLBITS);
463                 width = ir.getWidth();
464                 height = ir.getHeight();
465             }
466             else {
467                 width = img.getWidth(null);
468                 height = img.getHeight(null);
469             }
470             Dimension iconSize = getIconSize(width, height);
471             if (iconSize != null) {
472                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
473                     log.finest("Icon size: {0}", iconSize);
474                 }
475                 iconWidth = iconSize.width;
476                 iconHeight = iconSize.height;
477             } else {
478                 log.finest("Error calculating image size");
479                 iconWidth = 0;
480                 iconHeight = 0;
481             }
482             replaceImage(img);
483             replaceMask(img);
484         }
485         //create icon window and set XWMHints
486         XToolkit.awtLock();
487         try {
488             AwtGraphicsConfigData adata = parent.getGraphicsConfigurationData();
489             awtImageData awtImage = adata.get_awtImage(0);
490             XVisualInfo visInfo = adata.get_awt_visInfo();
491             XWMHints hints = parent.getWMHints();
492             window = hints.get_icon_window();
493             if (window == 0) {
494                 log.finest("Icon window wasn't set");
495                 XCreateWindowParams params = getDelayedParams();
496                 params.add(BORDER_PIXEL, Long.valueOf(0));
497                 params.add(BACKGROUND_PIXMAP, iconPixmap);
498                 params.add(COLORMAP, adata.get_awt_cmap());
499                 params.add(DEPTH, awtImage.get_Depth());
500                 params.add(VISUAL_CLASS, XConstants.InputOutput);
501                 params.add(VISUAL, visInfo.get_visual());
502                 params.add(VALUE_MASK, XConstants.CWBorderPixel | XConstants.CWColormap | XConstants.CWBackPixmap);
503                 params.add(PARENT_WINDOW, XlibWrapper.RootWindow(XToolkit.getDisplay(), visInfo.get_screen()));
504                 params.add(BOUNDS, new Rectangle(0, 0, iconWidth, iconHeight));
505                 params.remove(DELAYED);
506                 init(params);
507                 if (getWindow() == 0) {
508                     log.finest("Can't create new icon window");
509                 } else {
510                     log.finest("Created new icon window");
511                 }
512             }
513             if (getWindow() != 0) {
514                 XlibWrapper.XSetWindowBackgroundPixmap(XToolkit.getDisplay(), getWindow(), iconPixmap);
515                 XlibWrapper.XClearWindow(XToolkit.getDisplay(), getWindow());
516             }
517             // Provide both pixmap and window, WM or Taskbar will use the one they find more appropriate
518             long newFlags = hints.get_flags() | XUtilConstants.IconPixmapHint | XUtilConstants.IconMaskHint;
519             if (getWindow()  != 0) {
520                 newFlags |= XUtilConstants.IconWindowHint;
521             }
522             hints.set_flags(newFlags);
523             hints.set_icon_pixmap(iconPixmap);
524             hints.set_icon_mask(iconMask);
525             hints.set_icon_window(getWindow());
526             XlibWrapper.XSetWMHints(XToolkit.getDisplay(), parent.getShell(), hints.pData);
527             log.finest("Set icon window hint");
528         } finally {
529             XToolkit.awtUnlock();
530         }
531     }
532 
paddedwidth(int number, int boundary)533     static int paddedwidth(int number, int boundary)
534     {
535         return (((number) + ((boundary) - 1)) & (~((boundary) - 1)));
536     }
537 }
538