1
/********************************************************************
2
KWin - the KDE window manager
3
This file is part of the KDE project.
5
Copyright (C) 2010 Martin Gräßlin <kde@martin-graesslin.com>
7
This program is free software; you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 2 of the License, or
10
(at your option) any later version.
12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with this program. If not, see <http://www.gnu.org/licenses/>.
19
*********************************************************************/
20
#include "startupfeedback.h"
22
#include <QtCore/QSize>
23
#include <QtGui/QPainter>
25
#include <KDE/KConfigGroup>
27
#include <KDE/KGlobal>
28
#include <KDE/KIconLoader>
29
#include <KDE/KStandardDirs>
30
#include <KDE/KStartupInfo>
31
#include <KDE/KSelectionOwner>
33
#include <kwinglutils.h>
35
#include <X11/Xcursor/Xcursor.h>
37
// based on StartupId in KRunner by Lubos Lunak
38
// Copyright (C) 2001 Lubos Lunak <l.lunak@kde.org>
43
KWIN_EFFECT(startupfeedback, StartupFeedbackEffect)
44
KWIN_EFFECT_SUPPORTED(startupfeedback, StartupFeedbackEffect::supported())
46
// number of key frames for bouncing animation
47
static const int BOUNCE_FRAMES = 20;
48
// duration between two key frames in msec
49
static const int BOUNCE_FRAME_DURATION = 30;
50
// duration of one bounce animation
51
static const int BOUNCE_DURATION = BOUNCE_FRAME_DURATION * BOUNCE_FRAMES;
52
// number of key frames for blinking animation
53
static const int BLINKING_FRAMES = 5;
54
// duration between two key frames in msec
55
static const int BLINKING_FRAME_DURATION = 100;
56
// duration of one blinking animation
57
static const int BLINKING_DURATION = BLINKING_FRAME_DURATION * BLINKING_FRAMES;
58
//const int color_to_pixmap[] = { 0, 1, 2, 3, 2, 1 };
59
static const int FRAME_TO_BOUNCE_YOFFSET[] = {
60
-5, -1, 2, 5, 8, 10, 12, 13, 15, 15, 15, 15, 14, 12, 10, 8, 5, 2, -1, -5
62
static const QSize BOUNCE_SIZES[] = {
63
QSize(16, 16), QSize(14, 18), QSize(12, 20), QSize(18, 14), QSize(20, 12)
65
static const int FRAME_TO_BOUNCE_TEXTURE[] = {
66
0, 0, 0, 1, 2, 2, 1, 0, 3, 4, 4, 3, 0, 1, 2, 2, 1, 0, 0, 0
68
static const int FRAME_TO_BLINKING_COLOR[] = {
71
static const QColor BLINKING_COLORS[] = {
72
Qt::black, Qt::darkGray, Qt::lightGray, Qt::white, Qt::white
75
StartupFeedbackEffect::StartupFeedbackEffect()
76
: m_startupInfo(new KStartupInfo(KStartupInfo::CleanOnCantDetect, this))
77
, m_selection(new KSelectionOwner("_KDE_STARTUP_FEEDBACK", -1, this))
82
, m_type(BouncingFeedback)
85
for (int i = 0; i < 5; ++i) {
86
m_bouncingTextures[i] = 0;
88
m_selection->claim(true);
89
connect(m_startupInfo, SIGNAL(gotNewStartup(KStartupInfoId, KStartupInfoData)), SLOT(gotNewStartup(KStartupInfoId, KStartupInfoData)));
90
connect(m_startupInfo, SIGNAL(gotRemoveStartup(KStartupInfoId, KStartupInfoData)), SLOT(gotRemoveStartup(KStartupInfoId, KStartupInfoData)));
91
connect(m_startupInfo, SIGNAL(gotStartupChange(KStartupInfoId, KStartupInfoData)), SLOT(gotStartupChange(KStartupInfoId, KStartupInfoData)));
92
connect(effects, SIGNAL(mouseChanged(QPoint,QPoint,Qt::MouseButtons,Qt::MouseButtons,Qt::KeyboardModifiers,Qt::KeyboardModifiers)),
93
this, SLOT(slotMouseChanged(QPoint,QPoint,Qt::MouseButtons,Qt::MouseButtons,Qt::KeyboardModifiers,Qt::KeyboardModifiers)));
94
reconfigure(ReconfigureAll);
97
StartupFeedbackEffect::~StartupFeedbackEffect()
100
effects->stopMousePolling();
102
for (int i = 0; i < 5; ++i) {
103
delete m_bouncingTextures[i];
106
delete m_blinkingShader;
109
bool StartupFeedbackEffect::supported()
111
return effects->compositingType() == OpenGLCompositing;
114
void StartupFeedbackEffect::reconfigure(Effect::ReconfigureFlags flags)
117
KConfig conf("klaunchrc", KConfig::NoGlobals);
118
KConfigGroup c = conf.group("FeedbackStyle");
119
const bool busyCursor = c.readEntry("BusyCursor", true);
121
c = conf.group("BusyCursorSettings");
122
m_startupInfo->setTimeout(c.readEntry("Timeout", 30));
123
const bool busyBlinking = c.readEntry("Blinking", false);
124
const bool busyBouncing = c.readEntry("Bouncing", true);
127
else if (busyBouncing)
128
m_type = BouncingFeedback;
129
else if (busyBlinking) {
130
m_type = BlinkingFeedback;
131
if (ShaderManager::instance()->isValid()) {
132
delete m_blinkingShader;
133
m_blinkingShader = 0;
134
const QString shader = KGlobal::dirs()->findResource("data", "kwin/blinking-startup-fragment.glsl");
135
m_blinkingShader = ShaderManager::instance()->loadFragmentShader(ShaderManager::SimpleShader, shader);
136
if (m_blinkingShader->isValid()) {
137
kDebug(1212) << "Blinking Shader is valid";
139
kDebug(1212) << "Blinking Shader is not valid";
143
m_type = PassiveFeedback;
146
start(m_startups[ m_currentStartup ]);
150
void StartupFeedbackEffect::prePaintScreen(ScreenPrePaintData& data, int time)
153
// need the unclipped version
155
case BouncingFeedback:
156
m_progress = (m_progress + time) % BOUNCE_DURATION;
157
m_frame = qRound((qreal)m_progress / (qreal)BOUNCE_FRAME_DURATION) % BOUNCE_FRAMES;;
159
case BlinkingFeedback:
160
m_progress = (m_progress + time) % BLINKING_DURATION;
161
m_frame = qRound((qreal)m_progress / (qreal)BLINKING_FRAME_DURATION) % BLINKING_FRAMES;
166
m_currentGeometry = feedbackRect();
167
data.paint.unite(m_currentGeometry);
169
effects->prePaintScreen(data, time);
172
void StartupFeedbackEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data)
174
effects->paintScreen(mask, region, data);
178
case BouncingFeedback:
179
texture = m_bouncingTextures[ FRAME_TO_BOUNCE_TEXTURE[ m_frame ]];
181
case BlinkingFeedback: // fall through
182
case PassiveFeedback:
188
#ifndef KWIN_HAVE_OPENGLES
189
glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT);
192
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
194
bool useShader = false;
195
if (m_type == BlinkingFeedback) {
196
const QColor& blinkingColor = BLINKING_COLORS[ FRAME_TO_BLINKING_COLOR[ m_frame ]];
197
if (m_blinkingShader && m_blinkingShader->isValid()) {
199
ShaderManager::instance()->pushShader(m_blinkingShader);
200
m_blinkingShader->setUniform("u_color", blinkingColor);
202
#ifndef KWIN_HAVE_OPENGLES
203
// texture transformation
204
float color[4] = { blinkingColor.redF(), blinkingColor.greenF(), blinkingColor.blueF(), 1.0f };
205
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
207
glActiveTexture(GL_TEXTURE1);
209
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
210
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);
211
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
212
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
213
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_CONSTANT);
214
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
215
glActiveTexture(GL_TEXTURE0);
216
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);
219
} else if (ShaderManager::instance()->isValid()) {
221
ShaderManager::instance()->pushShader(ShaderManager::SimpleShader);
223
texture->render(m_currentGeometry, m_currentGeometry);
225
ShaderManager::instance()->popShader();
227
if (m_type == BlinkingFeedback && !useShader) {
228
#ifndef KWIN_HAVE_OPENGLES
230
glActiveTexture(GL_TEXTURE1);
232
glActiveTexture(GL_TEXTURE0);
233
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
234
glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
239
#ifndef KWIN_HAVE_OPENGLES
245
void StartupFeedbackEffect::postPaintScreen()
249
case BouncingFeedback: // fall through
250
case BlinkingFeedback:
252
effects->addRepaint(m_currentGeometry);
254
case PassiveFeedback: // fall through
256
// no need to repaint - no change
260
effects->postPaintScreen();
263
void StartupFeedbackEffect::slotMouseChanged(const QPoint& pos, const QPoint& oldpos, Qt::MouseButtons buttons,
264
Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers)
271
Q_UNUSED(oldmodifiers)
273
effects->addRepaint(m_currentGeometry);
274
effects->addRepaint(feedbackRect());
278
void StartupFeedbackEffect::gotNewStartup(const KStartupInfoId& id, const KStartupInfoData& data)
280
const QString& icon = data.findIcon();
281
m_currentStartup = id;
282
m_startups[ id ] = icon;
286
void StartupFeedbackEffect::gotRemoveStartup(const KStartupInfoId& id, const KStartupInfoData& data)
289
m_startups.remove(id);
290
if (m_startups.count() == 0) {
291
m_currentStartup = KStartupInfoId(); // null
295
m_currentStartup = m_startups.begin().key();
296
start(m_startups[ m_currentStartup ]);
299
void StartupFeedbackEffect::gotStartupChange(const KStartupInfoId& id, const KStartupInfoData& data)
301
if (m_currentStartup == id) {
302
const QString& icon = data.findIcon();
303
if (!icon.isEmpty() && icon != m_startups[ m_currentStartup ]) {
304
m_startups[ id ] = icon;
310
void StartupFeedbackEffect::start(const QString& icon)
312
if (m_type == NoFeedback)
315
effects->startMousePolling();
317
QPixmap iconPixmap = KIconLoader::global()->loadIcon(icon, KIconLoader::Small, 0,
318
KIconLoader::DefaultState, QStringList(), 0, true); // return null pixmap if not found
319
if (iconPixmap.isNull())
320
iconPixmap = SmallIcon("system-run");
321
prepareTextures(iconPixmap);
322
effects->addRepaintFull();
325
void StartupFeedbackEffect::stop()
328
effects->stopMousePolling();
331
case BouncingFeedback:
332
for (int i = 0; i < 5; ++i) {
333
delete m_bouncingTextures[i];
334
m_bouncingTextures[i] = 0;
337
case BlinkingFeedback:
338
case PassiveFeedback:
343
return; // don't want the full repaint
347
effects->addRepaintFull();
350
void StartupFeedbackEffect::prepareTextures(const QPixmap& pix)
353
case BouncingFeedback:
354
for (int i = 0; i < 5; ++i) {
355
delete m_bouncingTextures[i];
356
m_bouncingTextures[i] = new GLTexture(scalePixmap(pix, BOUNCE_SIZES[i]));
359
case BlinkingFeedback:
360
case PassiveFeedback:
361
m_texture = new GLTexture(pix);
370
QImage StartupFeedbackEffect::scalePixmap(const QPixmap& pm, const QSize& size) const
372
QImage scaled = pm.toImage().scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
373
if (scaled.format() != QImage::Format_ARGB32_Premultiplied && scaled.format() != QImage::Format_ARGB32)
374
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
376
QImage result(20, 20, QImage::Format_ARGB32);
378
p.setCompositionMode(QPainter::CompositionMode_Source);
379
p.fillRect(result.rect(), Qt::transparent);
380
p.drawImage((20 - size.width()) / 2, (20 - size.height()) / 2, scaled, 0, 0, size.width(), size.height());
384
QRect StartupFeedbackEffect::feedbackRect() const
387
cursorSize = XcursorGetDefaultSize(QX11Info::display());
389
if (cursorSize <= 16)
391
else if (cursorSize <= 32)
393
else if (cursorSize <= 48)
398
GLTexture* texture = 0;
401
case BouncingFeedback:
402
texture = m_bouncingTextures[ FRAME_TO_BOUNCE_TEXTURE[ m_frame ]];
403
yOffset = FRAME_TO_BOUNCE_YOFFSET[ m_frame ];
405
case BlinkingFeedback: // fall through
406
case PassiveFeedback:
413
const QPoint cursorPos = effects->cursorPos() + QPoint(xDiff, yDiff + yOffset);
416
rect = QRect(cursorPos, texture->size());