2
* kate: space-indent on; tab-width 8; indent-width 4; indent-mode cstyle;
4
* This file is part of the KDE project, module kdesktop.
5
* Copyright (C) 1999 Geert Jansen <g.t.jansen@stud.tue.nl>
7
* You can Freely distribute this program under the GNU Library General
8
* Public License. See the file "COPYING.LIB" for the exact licensing terms.
13
#include <config-workspace.h>
24
#include <QDesktopWidget>
25
#include <QPaintEngine>
28
#include <QSvgRenderer>
30
#include <kapplication.h>
33
#include <kstandarddirs.h>
34
#include <qimageblitz.h>
35
#include <ktemporaryfile.h>
37
#include <kfilemetainfo.h>
38
#include <kconfiggroup.h>
39
#include <kmacroexpander.h>
41
#include "bgdefaults.h"
47
/**** KBackgroundRenderer ****/
50
KBackgroundRenderer::KBackgroundRenderer(int screen, bool drawBackgroundPerScreen, const KSharedConfigPtr &config)
51
: KBackgroundSettings(screen, drawBackgroundPerScreen, config)
54
m_isBusyCursor = false;
55
m_enableBusyCursor = false;
56
m_pDirs = KGlobal::dirs();
57
m_rSize = m_Size = drawBackgroundPerScreen ?
58
QApplication::desktop()->screenGeometry(screen).size() : QApplication::desktop()->size();
63
m_TilingEnabled = false;
65
m_pTimer = new QTimer(this);
66
m_pTimer->setSingleShot(true);
67
connect(m_pTimer, SIGNAL(timeout()), SLOT(render()));
71
KBackgroundRenderer::~KBackgroundRenderer()
79
void KBackgroundRenderer::setSize(const QSize &size)
81
m_rSize = m_Size = size;
85
* Re-configure because the desktop has been resized.
87
void KBackgroundRenderer::desktopResized()
90
m_rSize = drawBackgroundPerScreen() ?
91
QApplication::desktop()->screenGeometry(screen()).size() : QApplication::desktop()->size();
97
void KBackgroundRenderer::tile(QImage &dest, const QRect &_rect, const QImage &src)
103
int h = rect.height(), w = rect.width();
104
int offx = rect.x(), offy = rect.y();
105
int sw = src.width(), sh = src.height();
107
for (y = offy; y < offy + h; y++)
108
for (x = offx; x < offx + w; x++)
109
dest.setPixel(x, y, src.pixel(x % sw, y % sh));
114
* Build a command line to run the program.
117
QString KBackgroundRenderer::buildCommand()
121
cmd = previewCommand();
128
QHash<QChar, QString> map;
129
map.insert('f', m_Tempfile->fileName());
130
map.insert('x', QString::number(m_Size.width()));
131
map.insert('y', QString::number(m_Size.height()));
132
return KMacroExpander::expandMacrosShellQuote(cmd, map);
137
* Create a background tile. If the background mode is `Program',
138
* this is asynchronous.
140
int KBackgroundRenderer::doBackground(bool quit)
142
if (m_State & BackgroundDone)
144
int bgmode = backgroundMode();
150
if (bgmode == Program && m_pProc)
151
m_pProc->terminate();
158
static unsigned int tileWidth = 0;
159
static unsigned int tileHeight = 0;
160
if (tileWidth == 0) {
161
int tile_val = QPixmap::defaultDepth() >= 24 ? 1 : 2;
162
// some dithering may be needed even with bpb==15/16, so don't use tileWidth==1
164
// with tileWidth>2, repainting the desktop causes nasty effect (XFree86 4.1.0)
165
if (XQueryBestTile(QX11Info::display(), QX11Info::appRootWindow(), tile_val, tile_val,
166
&tileWidth, &tileHeight) != Success)
167
tileWidth = tileHeight = tile_val; // some defaults
172
// this can be tiled correctly without problems
173
m_Background = QImage(tileWidth, tileHeight, QImage::Format_RGB32);
174
m_Background.fill(colorA().rgb());
178
if (pattern().isEmpty())
180
file = m_pDirs->findResource("dtop_pattern", pattern());
184
m_Background.load(file);
185
if (m_Background.isNull())
187
int w = m_Background.width();
188
int h = m_Background.height();
189
if ((w > m_Size.width()) || (h > m_Size.height())) {
190
w = qMin(w, m_Size.width());
191
h = qMin(h, m_Size.height());
192
m_Background = m_Background.copy(0, 0, w, h);
194
Blitz::flatten(m_Background, colorA(), colorB());
198
if (m_State & BackgroundStarted)
200
m_State |= BackgroundStarted;
203
file = buildCommand();
208
m_pProc = new KProcess;
209
m_pProc->setShellCommand(file);
211
SIGNAL(finished(int, QProcess::ExitStatus)),
212
SLOT(slotBackgroundDone(int, QProcess::ExitStatus)));
217
case HorizontalGradient: {
219
// on <16bpp displays the gradient sucks when tiled because of dithering
221
size.setHeight(tileHeight);
222
m_Background = Blitz::gradient(size, colorA(), colorB(),
223
Blitz::HorizontalGradient);
226
case VerticalGradient: {
228
// on <16bpp displays the gradient sucks when tiled because of dithering
230
size.setWidth(tileWidth);
231
m_Background = Blitz::gradient(size, colorA(), colorB(),
232
Blitz::VerticalGradient);
235
case PyramidGradient:
236
m_Background = Blitz::gradient(m_Size, colorA(), colorB(),
237
Blitz::PyramidGradient);
240
case PipeCrossGradient:
241
m_Background = Blitz::gradient(m_Size, colorA(), colorB(),
242
Blitz::PipeCrossGradient);
245
case EllipticGradient:
246
m_Background = Blitz::gradient(m_Size, colorA(), colorB(),
247
Blitz::EllipticGradient);
252
m_State |= BackgroundDone;
258
int KBackgroundRenderer::doWallpaper(bool quit)
260
if (m_State & WallpaperDone)
264
// currently no asynch. wallpapers
267
int wpmode = enabled() ? wallpaperMode() : NoWallpaper;
269
m_Wallpaper = QImage();
270
if (wpmode != NoWallpaper) {
272
if (currentWallpaper().isEmpty()) {
273
wpmode = NoWallpaper;
276
QString file = m_pDirs->findResource("wallpaper", currentWallpaper());
277
if (file.isEmpty()) {
278
wpmode = NoWallpaper;
282
// _Don't_ use KMimeType, as it relies on ksycoca which we really
283
// don't want in krootimage (kdm context).
284
//if (KMimeType::findByPath(file)->is("image/svg+xml")) {
285
if (file.endsWith(".svg") || file.endsWith(".svgz")) {
287
// Special stuff for SVG icons
290
//ksvgiconloader doesn't seem to let us find out the
291
//ratio of width to height so for the most part we just
292
//assume it's a square
298
svgHeight = (int)(m_Size.height() * 0.8);
299
svgWidth = svgHeight;
303
svgHeight = (int)(m_Size.height() * 0.5);
304
svgWidth = svgHeight;
307
svgHeight = m_Size.height();
308
svgWidth = m_Size.width();
313
svgHeight = m_Size.height();
314
svgWidth = svgHeight;
318
kWarning() << "unknown diagram type" ;
319
svgHeight = m_Size.height();
320
svgWidth = svgHeight;
323
//FIXME hack due to strangeness with
324
//background control modules
325
if (svgHeight < 200) {
330
QSvgRenderer renderer(file);
331
if (renderer.isValid()) {
332
m_Wallpaper = QImage(svgWidth, svgHeight, QImage::Format_ARGB32_Premultiplied);
334
QPainter p(&m_Wallpaper);
338
m_Wallpaper.load(file);
340
if (m_Wallpaper.isNull()) {
341
kWarning() << "failed to load wallpaper " << file ;
342
if (discardCurrentWallpaper())
344
wpmode = NoWallpaper;
347
m_Wallpaper = m_Wallpaper.convertToFormat(QImage::Format_ARGB32_Premultiplied, Qt::DiffuseAlphaDither);
349
// If we're previewing, scale the wallpaper down to make the preview
350
// look more like the real desktop.
352
int xs = m_Wallpaper.width() * m_Size.width() / m_rSize.width();
353
int ys = m_Wallpaper.height() * m_Size.height() / m_rSize.height();
354
if ((xs < 1) || (ys < 1)) {
357
if (m_WallpaperRect.size() != QSize(xs, ys))
358
m_Wallpaper = m_Wallpaper.scaled(xs, ys, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
362
// HACK: Use KFileMetaInfo only when we have a KApplication
363
// KFileMetaInfo needs ksycoca and so on, but this code is
364
// used also in krootimage (which in turn is used by kdm).
366
KFileMetaInfo metaInfo(file);
367
if (metaInfo.isValid() && metaInfo.item("Orientation").isValid()) {
368
switch (metaInfo.item("Orientation").value().toInt()) {
370
// Flipped horizontally
371
m_Wallpaper.mirrored(true, false);
374
// Rotated 180 degrees
375
m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(180));
378
// Flipped vertically
379
m_Wallpaper = m_Wallpaper.mirrored(false, true);
382
// Rotated 90 degrees & flipped horizontally
383
m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(90)).mirrored(true, false);
386
// Rotated 90 degrees
387
m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(90));
390
// Rotated 90 degrees & flipped vertically
391
m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(90)).mirrored(false, true);
394
// Rotated 270 degrees
395
m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(270));
399
// Normal or invalid orientation
408
if (m_Background.isNull()) {
409
m_Background = QImage(8, 8, QImage::Format_RGB32);
410
m_Background.fill(colorA().rgb());
415
// desktop width/height
416
int w = m_Size.width();
417
int h = m_Size.height();
419
// wallpaper width/height
420
int ww = m_Wallpaper.width();
421
int wh = m_Wallpaper.height();
423
// to be filled destination rectangle; may exceed desktop!
424
m_WallpaperRect = QRect();
430
m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, ww, wh);
433
m_WallpaperRect.setRect(0, 0, w, h);
436
m_WallpaperRect.setCoords(-ww + ((w - ww) / 2) % ww, -wh + ((h - wh) / 2) % wh, w - 1, h - 1);
441
if (m_WallpaperRect.size() != QSize(w, h))
442
m_Wallpaper = m_Wallpaper.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
443
m_WallpaperRect.setRect(0, 0, w, h);
446
if (ww <= w && wh <= h) {
447
m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, ww, wh); // like Centred
451
case CentredMaxpect: {
452
double sx = (double) w / ww;
453
double sy = (double) h / wh;
461
if (m_WallpaperRect.size() != QSize(ww, wh))
462
m_Wallpaper = m_Wallpaper.scaled(ww, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
463
m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, ww, wh);
467
double sx = (double) w / ww;
468
double sy = (double) h / wh;
476
if (m_WallpaperRect.size() != QSize(ww, wh))
477
m_Wallpaper = m_Wallpaper.scaled(ww, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
478
m_WallpaperRect.setRect(0, 0, w, h);
482
double sx = (double) w / ww;
483
double sy = (double) h / wh;
485
//Case 1: x needs bigger scaling. Lets increase x and leave part of y offscreen
489
//Case 2: y needs bigger scaling. Lets increase y and leave part of x offscreen
493
if (m_WallpaperRect.size() != QSize(ww, wh))
494
m_Wallpaper = m_Wallpaper.scaled(ww, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
495
m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, w, h);
503
m_State |= WallpaperDone;
508
bool KBackgroundRenderer::canTile() const
510
return m_TilingEnabled && optimize();
513
void KBackgroundRenderer::wallpaperBlend()
515
if (!enabled() || wallpaperMode() == NoWallpaper
516
|| (blendMode() == NoBlending &&
517
(QApplication::desktop()->paintEngine()->hasFeature(QPaintEngine::Antialiasing)
518
|| !m_Wallpaper.hasAlphaChannel()))) {
519
fastWallpaperBlend();
521
fullWallpaperBlend();
525
// works only for NoBlending and no alpha in wallpaper
526
// but is much faster than QImage fidling
527
void KBackgroundRenderer::fastWallpaperBlend()
530
// copy background to m_pPixmap
531
if (!enabled() || (wallpaperMode() == NoWallpaper && canTile())) {
532
// if there's no wallpaper, no need to tile the pixmap to the size of desktop, as X does
533
// that automatically and using a smaller pixmap should save some memory
534
m_Pixmap = QPixmap::fromImage(m_Background);
536
} else if (wallpaperMode() == Tiled && !m_Wallpaper.hasAlphaChannel() && canTile() && !m_bPreview) {
537
// tiles will be tiled by X automatically
538
m_Pixmap = QPixmap::fromImage(m_Wallpaper);
540
} else if (m_WallpaperRect.contains(QRect(QPoint(0, 0), m_Size))
541
&& !m_Wallpaper.hasAlphaChannel()) // wallpaper covers all and no blending
542
m_Pixmap = QPixmap(m_Size);
543
else if (m_Background.size() == m_Size)
544
m_Pixmap = QPixmap::fromImage(m_Background);
546
m_Pixmap = QPixmap(m_Size);
547
QPainter p(&m_Pixmap);
548
QPixmap pm = QPixmap::fromImage(m_Background);
549
p.drawTiledPixmap(0, 0, m_Size.width(), m_Size.height(), pm);
552
// paint/alpha-blend wallpaper to destination rectangle of m_pPixmap
553
if (m_WallpaperRect.isValid()) {
554
QPixmap wp_pixmap = QPixmap::fromImage(m_Wallpaper);
555
QPainter pa(&m_Pixmap);
556
int ww = m_Wallpaper.width();
557
int wh = m_Wallpaper.height();
558
for (int y = m_WallpaperRect.top(); y < m_WallpaperRect.bottom(); y += wh) {
559
for (int x = m_WallpaperRect.left(); x < m_WallpaperRect.right(); x += ww) {
560
pa.drawPixmap(x, y, wp_pixmap);
567
void KBackgroundRenderer::fullWallpaperBlend()
569
m_Pixmap = QPixmap();
571
// desktop width/height
572
int w = m_Size.width();
573
int h = m_Size.height();
575
// copy background to m_pImage
576
if (m_Background.size() == m_Size) {
577
m_Image = m_Background.copy();
579
if (m_Image.depth() < 32) {
580
m_Image = m_Image.convertToFormat(QImage::Format_ARGB32_Premultiplied, Qt::DiffuseAlphaDither);
583
m_Image = QImage(w, h, QImage::Format_RGB32);
584
tile(m_Image, QRect(0, 0, w, h), m_Background);
587
// blend wallpaper to destination rectangle of m_pImage
588
if (m_WallpaperRect.isValid()) {
589
int blendFactor = 100;
590
if (blendMode() == FlatBlending)
591
blendFactor = (blendBalance() + 200) / 4;
592
int ww = m_Wallpaper.width();
593
int wh = m_Wallpaper.height();
594
for (int y = m_WallpaperRect.top(); y < m_WallpaperRect.bottom(); y += wh) {
595
for (int x = m_WallpaperRect.left(); x < m_WallpaperRect.right(); x += ww) {
596
blend(m_Image, QRect(x, y, ww, wh), m_Wallpaper,
597
QPoint(-qMin(x, 0), -qMin(y, 0)), blendFactor);
603
// blend whole desktop
604
if (wallpaperMode() != NoWallpaper) {
605
int bal = blendBalance();
607
switch (blendMode()) {
608
/* TODO disabled for now
609
case HorizontalBlending:
610
Blitz::blend(m_Image, m_Background,
611
Blitz::HorizontalGradient,
615
case VerticalBlending:
616
Blitz::blend(m_Image, m_Background,
617
Blitz::VerticalGradient,
621
case PyramidBlending:
622
Blitz::blend(m_Image, m_Background,
623
Blitz::PyramidGradient,
627
case PipeCrossBlending:
628
Blitz::blend(m_Image, m_Background,
629
Blitz::PipeCrossGradient,
633
case EllipticBlending:
634
Blitz::blend(m_Image, m_Background,
635
Blitz::EllipticGradient,
640
case IntensityBlending:
641
Blitz::modulate(m_Image, m_Background, reverseBlending(),
642
Blitz::Intensity, bal, Blitz::All);
645
case SaturateBlending:
646
Blitz::modulate(m_Image, m_Background, reverseBlending(),
647
Blitz::Saturation, bal, Blitz::Brightness);
650
case ContrastBlending:
651
Blitz::modulate(m_Image, m_Background, reverseBlending(),
652
Blitz::Contrast, bal, Blitz::All);
655
case HueShiftBlending:
656
Blitz::modulate(m_Image, m_Background, reverseBlending(),
657
Blitz::HueShift, bal, Blitz::Brightness);
667
/* Alpha blend an area from <src> with offset <soffs> to rectangle <dr> of <dst>
668
* Default offset is QPoint(0, 0).
669
* blendfactor = [0, 100%]
671
void KBackgroundRenderer::blend(QImage &dst, const QRect &_dr, const QImage &src, const QPoint &soffs, int blendFactor)
677
for (y = 0; y < dr.height(); y++) {
678
if (dst.scanLine(dr.y() + y) && src.scanLine(soffs.y() + y)) {
681
for (x = 0; x < dr.width(); x++) {
682
b = reinterpret_cast<QRgb *>(dst.scanLine(dr.y() + y)
683
+ (dr.x() + x) * sizeof(QRgb));
684
d = reinterpret_cast<const QRgb *>(src.scanLine(soffs.y() + y)
685
+ (soffs.x() + x) * sizeof(QRgb));
686
a = (qAlpha(*d) * blendFactor) / 100;
687
*b = qRgb(qRed(*b) - (((qRed(*b) - qRed(*d)) * a) >> 8),
688
qGreen(*b) - (((qGreen(*b) - qGreen(*d)) * a) >> 8),
689
qBlue(*b) - (((qBlue(*b) - qBlue(*d)) * a) >> 8));
697
void KBackgroundRenderer::slotBackgroundDone(int exitCode, QProcess::ExitStatus exitStatus)
699
m_State |= BackgroundDone;
701
if (exitStatus == QProcess::NormalExit && !exitCode) {
702
m_Background.load(m_Tempfile->fileName());
703
m_State |= BackgroundDone;
709
setBusyCursor(false);
715
* Starts the rendering process.
717
void KBackgroundRenderer::start(bool enableBusyCursor)
719
m_enableBusyCursor = enableBusyCursor;
730
* This slot is connected to a timer event. It is called repeatedly until
731
* the rendering is done.
733
void KBackgroundRenderer::render()
736
if (!(m_State & Rendering))
739
if (!(m_State & InitCheck)) {
740
QString f = cacheFileName();
741
if (useCacheFile()) {
742
QString w = m_pDirs->findResource("wallpaper", currentWallpaper());
745
if (wi.lastModified().isValid() && fi.lastModified().isValid()
746
&& wi.lastModified() < fi.lastModified()) {
748
if (im.load(f, "PNG")) {
750
m_Pixmap = QPixmap::fromImage(m_Image);
752
m_State |= InitCheck | BackgroundDone | WallpaperDone;
757
m_State |= InitCheck;
763
if (!(m_State & BackgroundDone)) {
764
ret = doBackground();
770
// No async wallpaper
774
setBusyCursor(false);
779
* Rendering is finished.
781
void KBackgroundRenderer::done()
783
setBusyCursor(false);
785
emit imageDone(screen());
786
if (backgroundMode() == Program && m_pProc &&
787
m_pProc->exitStatus() != QProcess::NormalExit) {
788
emit programFailure(-1);
789
} else if (backgroundMode() == Program && m_pProc &&
790
m_pProc->exitCode()) {
791
emit programFailure(m_pProc->exitStatus());
792
} else if (backgroundMode() == Program) {
793
emit programSuccess();
798
* This function toggles a busy cursor on and off, for use in rendering.
799
* It is useful because of the ASYNC nature of the rendering - it is hard
800
* to make sure we don't set the busy cursor twice, but only restore
803
void KBackgroundRenderer::setBusyCursor(bool isBusy)
805
if (m_isBusyCursor == isBusy)
807
if (isBusy && !m_enableBusyCursor)
809
m_isBusyCursor = isBusy;
811
QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
813
QApplication::restoreOverrideCursor();
817
* Stop the rendering.
819
void KBackgroundRenderer::stop()
821
if (!(m_State & Rendering))
831
* Cleanup after rendering.
833
void KBackgroundRenderer::cleanup()
835
setBusyCursor(false);
836
m_Background = QImage();
838
m_Pixmap = QPixmap();
839
m_Wallpaper = QImage();
843
m_WallpaperRect = QRect();
848
void KBackgroundRenderer::setPreview(const QSize &size)
859
QPixmap KBackgroundRenderer::pixmap()
861
if (m_State & AllDone) {
862
if (m_Pixmap.isNull())
863
m_Pixmap = QPixmap::fromImage(m_Image);
869
QImage KBackgroundRenderer::image()
871
if (m_State & AllDone) {
872
if (m_Image.isNull())
873
fullWallpaperBlend(); // create from m_Pixmap
880
void KBackgroundRenderer::load(int screen, bool drawBackgroundPerScreen, bool reparseConfig)
882
if (m_State & Rendering)
889
KBackgroundSettings::load(screen, drawBackgroundPerScreen, reparseConfig);
892
void KBackgroundRenderer::createTempFile()
895
m_Tempfile = new KTemporaryFile();
900
QString KBackgroundRenderer::cacheFileName()
902
QString f = fingerprint();
903
f.replace(':', '_'); // avoid characters that shouldn't be in filenames
905
f = KStandardDirs::locateLocal("cache", QString("background/%1x%2_%3.png")
906
.arg(m_Size.width()).arg(m_Size.height()).arg(f));
910
bool KBackgroundRenderer::useCacheFile() const
914
if (backgroundMode() == Program)
915
return false; // don't cache these at all
916
if (wallpaperMode() == NoWallpaper)
917
return false; // generating only background patterns should be always faster
918
QString file = currentWallpaper();
919
if (file.endsWith(".svg") || file.endsWith(".svgz"))
920
return true; // cache these, they can be bloody slow
921
switch (backgroundMode()) {
926
return false; // these don't need scaling
937
void KBackgroundRenderer::saveCacheFile()
939
if (!(m_State & AllDone))
943
if (m_Image.isNull())
944
fullWallpaperBlend(); // generate from m_Pixmap
945
QString f = cacheFileName();
946
if (KStandardDirs::exists(f) || m_Cached)
947
utime(QFile::encodeName(f), NULL);
949
m_Image.save(f, "PNG");
950
// remove old entries from the cache
951
QDir dir(KStandardDirs::locateLocal("cache", "background/"));
952
const QFileInfoList list = dir.entryInfoList(QStringList() << "*.png", QDir::Files, QDir::Time | QDir::Reversed);
953
if (!list.isEmpty()) {
955
foreach (const QFileInfo &info, list)
957
foreach (const QFileInfo &info, list) {
958
if (size < 8 * 1024 * 1024)
960
// keep everything newer than 10 minutes if the total size is less than 50M (just in case)
961
if (size < 50 * 1024 * 1024
962
&& (time_t) info.lastModified().toTime_t() >= time(NULL) - 10 * 60)
965
QFile::remove(info.absoluteFilePath());
971
#include "bgrender.moc"