1 // FbPixmap.cc for FbTk - Fluxbox ToolKit
2 // Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a
5 // copy of this software and associated documentation files (the "Software"),
6 // to deal in the Software without restriction, including without limitation
7 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 // and/or sell copies of the Software, and to permit persons to whom the
9 // Software is furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21 
22 #include "FbPixmap.hh"
23 #include "App.hh"
24 #include "GContext.hh"
25 #include "Transparent.hh"
26 #include "FbWindow.hh"
27 #include "TextUtils.hh"
28 
29 #include <X11/Xutil.h>
30 #include <X11/Xatom.h>
31 #include <iostream>
32 #include <vector>
33 #ifdef HAVE_CSTRING
34   #include <cstring>
35 #else
36   #include <string.h>
37 #endif
38 
39 using std::cerr;
40 
41 namespace FbTk {
42 
43 namespace {
44 
45 std::vector <Pixmap> s_root_pixmaps;
46 
47 struct RootProps {
48     const char* name;
49     Atom atom;
50 };
51 
52 struct RootProps root_props[] = {
53     { "_XROOTPMAP_ID", None },
54     { "_XSETROOT_ID", None }
55 };
56 
checkAtoms()57 void checkAtoms() {
58 
59     Display* display = FbTk::App::instance()->display();
60     for (size_t i = 0; i < sizeof(root_props)/sizeof(RootProps); ++i) {
61         if (root_props[i].atom == None) {
62             root_props[i].atom = XInternAtom(display, root_props[i].name, False);
63         }
64     }
65 }
66 
67 } // end of anonymous namespace
68 
FbPixmap()69 FbPixmap::FbPixmap():m_pm(0),
70                      m_width(0), m_height(0),
71                      m_depth(0), m_dont_free(false) {
72 }
73 
FbPixmap(const FbPixmap & the_copy)74 FbPixmap::FbPixmap(const FbPixmap &the_copy):FbDrawable(), m_pm(0),
75                                              m_width(0), m_height(0),
76                                              m_depth(0), m_dont_free(false) {
77     copy(the_copy);
78 }
79 
FbPixmap(Pixmap pm)80 FbPixmap::FbPixmap(Pixmap pm):m_pm(0),
81                               m_width(0), m_height(0),
82                               m_depth(0), m_dont_free(false) {
83     if (pm == 0)
84         return;
85     // assign X pixmap to this
86     (*this) = pm;
87 }
88 
FbPixmap(const FbDrawable & src,unsigned int width,unsigned int height,unsigned int depth)89 FbPixmap::FbPixmap(const FbDrawable &src,
90                    unsigned int width, unsigned int height,
91                    unsigned int depth):m_pm(0),
92                               m_width(0), m_height(0),
93                               m_depth(0), m_dont_free(false) {
94 
95     create(src.drawable(), width, height, depth);
96 }
97 
FbPixmap(Drawable src,unsigned int width,unsigned int height,unsigned int depth)98 FbPixmap::FbPixmap(Drawable src,
99                    unsigned int width, unsigned int height,
100                    unsigned int depth):m_pm(0),
101                               m_width(0), m_height(0),
102                               m_depth(0), m_dont_free(false) {
103 
104     create(src, width, height, depth);
105 }
106 
~FbPixmap()107 FbPixmap::~FbPixmap() {
108     free();
109 }
110 
operator =(const FbPixmap & the_copy)111 FbPixmap &FbPixmap::operator = (const FbPixmap &the_copy) {
112     copy(the_copy);
113     return *this;
114 }
115 
operator =(Pixmap pm)116 FbPixmap &FbPixmap::operator = (Pixmap pm) {
117     // free pixmap before we set new
118     free();
119 
120     if (pm == 0)
121         return *this;
122 
123     // get width, height and depth for the pixmap
124     Window root;
125     int x, y;
126     unsigned int border_width, bpp;
127     if (!XGetGeometry(display(),
128                       pm,
129                       &root,
130                       &x, &y,
131                       &m_width, &m_height,
132                       &border_width,
133                       &bpp))
134         return *this;
135 
136     m_depth = bpp;
137 
138     m_pm = pm;
139 
140     return *this;
141 }
142 
copy(const FbPixmap & the_copy)143 void FbPixmap::copy(const FbPixmap &the_copy) {
144     /* This function previously retained the old pixmap and copied in
145        the new contents if they had the same dimensions.
146        This broke the image cache, so we don't do that now. If you want to
147        do it, then you'll need to invalidate all copies of this pixmap in
148        the cache */
149     free();
150 
151     if (the_copy.drawable() != 0) {
152         create(the_copy.drawable(),
153                the_copy.width(), the_copy.height(),
154                the_copy.depth());
155 
156         if (drawable()) {
157             GContext gc(drawable());
158 
159             copyArea(the_copy.drawable(),
160                      gc.gc(),
161                      0, 0,
162                      0, 0,
163                      width(), height());
164         }
165     }
166 }
167 
168 // screen doesn't count if depth is "zero"...
copy(Pixmap pm,unsigned int depth,int screen_num)169 void FbPixmap::copy(Pixmap pm, unsigned int depth, int screen_num) {
170     free();
171     if (pm == 0)
172         return;
173 
174     // get width, height and depth for the pixmap
175     Window root;
176     int x, y;
177     unsigned int border_width, bpp;
178     unsigned int new_width, new_height;
179 
180     if (!XGetGeometry(display(),
181                       pm,
182                       &root,
183                       &x, &y,
184                       &new_width, &new_height,
185                       &border_width,
186                       &bpp))
187         return;
188 
189     if (depth == 0)
190         depth = bpp;
191 
192     // create new pixmap and copy area
193     create(root, new_width, new_height, depth);
194 
195     GC gc = XCreateGC(display(), drawable(), 0, 0);
196 
197     if (depth == bpp) {
198         XCopyArea(display(), pm, drawable(), gc,
199                   0, 0,
200                   width(), height(),
201                   0, 0);
202     } else {
203         XSetForeground(display(), gc, Color("black", screen_num).pixel());
204         XSetBackground(display(), gc, Color("white", screen_num).pixel());
205         XCopyPlane(display(), pm, drawable(), gc,
206                    0, 0,
207                    width(), height(),
208                    0, 0, 1);
209     }
210 
211     XFreeGC(display(), gc);
212 }
213 
rotate(FbTk::Orientation orient)214 void FbPixmap::rotate(FbTk::Orientation orient) {
215     if (orient == ROT0)
216         return;
217 
218     unsigned int oldw = width(), oldh = height();
219     unsigned int neww = oldw, newh = oldh;
220     translateSize(orient, neww, newh);
221 
222     // reverse height/width for new pixmap
223     FbPixmap new_pm(drawable(), neww, newh, depth());
224 
225     // width|height could be 0. this happens (for example) if
226     // the systemtray-tool is ROT90. in that case 'src_image'
227     // becomes NULL and caused a SIGSEV upon XDestroyImage()
228     // TODO: catch dimensions with '0' earlier?
229     //
230     // make an image copy
231     XImage *src_image = XGetImage(display(), drawable(),
232                                   0, 0, // pos
233                                   oldw, oldh, // size
234                                   ~0, // plane mask
235                                   ZPixmap); // format
236     if (src_image) {
237 
238         GContext gc(drawable());
239 
240         if (orient == ROT180) {
241             unsigned int srcx, srcy, destx, desty;
242             for (srcy = 0, desty = oldh; srcy < oldh; ++srcy, --desty) {
243                 for (srcx = 0, destx = oldw; srcx < oldw; ++srcx, --destx) {
244                     gc.setForeground(XGetPixel(src_image, srcx, srcy));
245                     XDrawPoint(display(), new_pm.drawable(), gc.gc(), destx, desty);
246                 }
247             }
248         } else {
249             // need to flip x and y
250 
251             // set start, end and direction based on rotation
252             // NOTE that startx etc are in the direction of the OLD pixmap
253             unsigned int startx = 0, starty = 0;
254             int dirx = 0, diry = 0;
255             switch (orient) {
256             case ROT90:
257                 startx = neww-1;
258                 starty = 0;
259                 dirx = -1;
260                 diry = 1;
261                 break;
262             case ROT270:
263                 startx = 0;
264                 starty = newh-1;
265                 dirx = 1;
266                 diry = -1;
267                 break;
268             default: // kill warning
269                 break;
270             }
271 
272 
273             // copy new area
274             unsigned int srcx, srcy, destx, desty;
275             for (srcy = 0, destx = startx; srcy < oldh; ++srcy, destx+=dirx) {
276                 for (srcx = 0, desty = starty; srcx < oldw; ++srcx, desty+=diry) {
277                     gc.setForeground(XGetPixel(src_image, srcx, srcy));
278                     XDrawPoint(display(), new_pm.drawable(), gc.gc(), destx, desty);
279                 }
280             }
281         }
282 
283         XDestroyImage(src_image);
284     }
285 
286     // free old pixmap and set new from new_pm
287     free();
288 
289     m_width = new_pm.width();
290     m_height = new_pm.height();
291     m_depth = new_pm.depth();
292     m_pm = new_pm.release();
293 }
294 
scale(unsigned int dest_width,unsigned int dest_height)295 void FbPixmap::scale(unsigned int dest_width, unsigned int dest_height) {
296 
297     if (drawable() == 0 ||
298         (dest_width == width() && dest_height == height()))
299         return;
300 
301     XImage *src_image = XGetImage(display(), drawable(),
302                                   0, 0, // pos
303                                   width(), height(), // size
304                                   ~0, // plane mask
305                                   ZPixmap); // format
306     if (src_image == 0)
307         return;
308 
309     // create new pixmap with dest size
310     FbPixmap new_pm(drawable(), dest_width, dest_height, depth());
311 
312     GContext gc(drawable());
313     // calc zoom
314     float zoom_x = static_cast<float>(width())/static_cast<float>(dest_width);
315     float zoom_y = static_cast<float>(height())/static_cast<float>(dest_height);
316 
317     // start scaling
318     float src_x = 0, src_y = 0;
319     for (unsigned int tx=0; tx < dest_width; ++tx, src_x += zoom_x) {
320         src_y = 0;
321         for (unsigned int ty=0; ty < dest_height; ++ty, src_y += zoom_y) {
322             gc.setForeground(XGetPixel(src_image,
323                                        static_cast<int>(src_x),
324                                        static_cast<int>(src_y)));
325             XDrawPoint(display(), new_pm.drawable(), gc.gc(), tx, ty);
326         }
327     }
328 
329     XDestroyImage(src_image);
330 
331     // free old pixmap and set new from new_pm
332     free();
333 
334     m_width = new_pm.width();
335     m_height = new_pm.height();
336     m_depth = new_pm.depth();
337     m_pm = new_pm.release();
338 }
339 
tile(unsigned int dest_width,unsigned int dest_height)340 void FbPixmap::tile(unsigned int dest_width, unsigned int dest_height) {
341     if (drawable() == 0 ||
342         (dest_width == width() && dest_height == height()))
343         return;
344 
345     FbPixmap new_pm(drawable(), width(), height(), depth());
346 
347     new_pm.copy(m_pm, 0, 0);
348 
349     resize(dest_width, dest_height);
350 
351     FbTk::GContext gc(*this);
352 
353     gc.setTile(new_pm);
354     gc.setFillStyle(FillTiled);
355 
356     fillRectangle(gc.gc(), 0, 0, dest_width, dest_height);
357 
358 }
359 
360 
361 
resize(unsigned int width,unsigned int height)362 void FbPixmap::resize(unsigned int width, unsigned int height) {
363     FbPixmap pm(drawable(), width, height, depth());
364     *this = pm.release();
365 }
366 
release()367 Pixmap FbPixmap::release() {
368     Pixmap ret = m_pm;
369     m_pm = 0;
370     m_width = 0;
371     m_height = 0;
372     m_depth = 0;
373     return ret;
374 }
375 
376 // returns whether or not the background was changed
rootwinPropertyNotify(int screen_num,Atom atom)377 bool FbPixmap::rootwinPropertyNotify(int screen_num, Atom atom) {
378     if (!FbTk::Transparent::haveRender())
379         return false;
380 
381     checkAtoms();
382     for (size_t i = 0; i < sizeof(root_props)/sizeof(RootProps); ++i) {
383         if (root_props[i].atom == atom) {
384             Pixmap root_pm = None;
385             Atom real_type;
386             int real_format;
387             unsigned long items_read, items_left;
388             unsigned long *data;
389 
390             if (XGetWindowProperty(display(),
391                                    RootWindow(display(), screen_num),
392                                    root_props[i].atom,
393                                    0l, 1l,
394                                    False, XA_PIXMAP,
395                                    &real_type, &real_format,
396                                    &items_read, &items_left,
397                                    (unsigned char **) &data) == Success) {
398                 if (real_format == 32 && items_read == 1) {
399                     root_pm = (Pixmap) (*data);
400                 }
401                 XFree(data);
402                 if (root_pm != None)
403                     return setRootPixmap(screen_num, root_pm);
404             }
405             return false;
406         }
407     }
408     return false;
409 }
410 
411 // returns whether or not the background was changed
setRootPixmap(int screen_num,Pixmap pm)412 bool FbPixmap::setRootPixmap(int screen_num, Pixmap pm) {
413 
414     if (s_root_pixmaps.empty()) {
415         int i;
416         for (i = 0; i < ScreenCount(display()); ++i)
417             s_root_pixmaps.push_back(None);
418     }
419 
420     if (s_root_pixmaps[screen_num] != pm) {
421         s_root_pixmaps[screen_num] = pm;
422         FbWindow::updatedAlphaBackground(screen_num);
423         return true;
424     }
425     return false;
426 }
427 
getRootPixmap(int screen_num,bool force_update)428 Pixmap FbPixmap::getRootPixmap(int screen_num, bool force_update) {
429     /*
430       if (!FbTk::Transparent::haveRender())
431       return None;
432     */
433 
434     // check and see if if we have the pixmaps in cache
435     if (!s_root_pixmaps.empty() && !force_update)
436         return s_root_pixmaps[screen_num];
437 
438     checkAtoms();
439 
440     // else setup pixmap cache
441     int numscreens = ScreenCount(display());
442     for (int i=0; i < numscreens; ++i) {
443         Atom real_type;
444         int real_format;
445         unsigned long items_read, items_left;
446         unsigned long *data;
447 
448         static bool print_error = true; // print error_message only once
449 
450         Pixmap root_pm = None;
451 
452         unsigned int prop = 0;
453         for (prop = 0; prop < sizeof(root_props)/sizeof(RootProps); ++prop) {
454             if (XGetWindowProperty(display(),
455                                    RootWindow(display(), i),
456                                    root_props[prop].atom,
457                                    0l, 1l,
458                                    False, XA_PIXMAP,
459                                    &real_type, &real_format,
460                                    &items_read, &items_left,
461                                    (unsigned char **) &data) == Success) {
462                 if (real_format == 32 && items_read == 1) {
463                     if (print_error && strcmp(root_props[prop].name, "_XSETROOT_ID") == 0) {
464                         static const char* error_message = {
465                             "\n\n !!! WARNING WARNING WARNING WARNING !!!!!\n"
466                             "   if you experience problems with transparency:\n"
467                             "   you are using a wallpapersetter that \n"
468                             "   uses _XSETROOT_ID .. which we do not support.\n"
469                             "   consult 'fbsetbg -i' or try any other wallpapersetter\n"
470                             "   that uses _XROOTPMAP_ID !\n"
471                             " !!! WARNING WARNING WARNING WARNING !!!!!!\n\n"
472                         };
473                         cerr<<error_message;
474                         print_error = false;
475                     } else
476                         root_pm = (Pixmap) (*data);
477                 }
478                 XFree(data);
479                 if (root_pm != None)
480                     break;
481             }
482         }
483         setRootPixmap(i, root_pm);
484     }
485 
486     return s_root_pixmaps[screen_num];
487 }
488 
free()489 void FbPixmap::free() {
490     if (!m_dont_free && m_pm != 0)
491         XFreePixmap(display(), m_pm);
492 
493     /* note: m_dont_free shouldnt be required anywhere else,
494        because then free() isn't being called appropriately! */
495     m_dont_free = false;
496     m_pm = 0;
497     m_width = 0;
498     m_height = 0;
499     m_depth = 0;
500 }
501 
create(Drawable src,unsigned int width,unsigned int height,unsigned int depth)502 void FbPixmap::create(Drawable src,
503                       unsigned int width, unsigned int height,
504                       unsigned int depth) {
505     if (src == 0)
506         return;
507 
508     m_pm = XCreatePixmap(display(),
509                          src, width, height, depth);
510     if (m_pm == 0)
511         return;
512 
513     m_width = width;
514     m_height = height;
515     m_depth = depth;
516 }
517 
518 } // end namespace FbTk
519 
520