12#include <QApplication>
13#include <QDBusConnectionInterface>
14#include <QDBusServiceWatcher>
18#include <QStandardPaths>
22#include <KConfigGroup>
23#include <KSelectionOwner>
24#include <KSharedConfig>
25#include <KWindowSystem>
37Q_LOGGING_CATEGORY(KWIN_STARTUPFEEDBACK,
"kwin_effect_startupfeedback", QtWarningMsg)
39static void ensureResources()
42 Q_INIT_RESOURCE(startupfeedback);
49static const int BOUNCE_FRAMES = 20;
51static const int BOUNCE_FRAME_DURATION = 30;
53static const int BOUNCE_DURATION = BOUNCE_FRAME_DURATION * BOUNCE_FRAMES;
55static const int BLINKING_FRAMES = 5;
57static const int BLINKING_FRAME_DURATION = 100;
59static const int BLINKING_DURATION = BLINKING_FRAME_DURATION * BLINKING_FRAMES;
61static const int FRAME_TO_BOUNCE_YOFFSET[] = {
62 -5, -1, 2, 5, 8, 10, 12, 13, 15, 15, 15, 15, 14, 12, 10, 8, 5, 2, -1, -5};
63static const QSize BOUNCE_SIZES[] = {
64 QSize(16, 16), QSize(14, 18), QSize(12, 20), QSize(18, 14), QSize(20, 12)};
65static 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};
67static const int FRAME_TO_BLINKING_COLOR[] = {
69static const QColor BLINKING_COLORS[] = {
70 Qt::black, Qt::darkGray, Qt::lightGray, Qt::white, Qt::white};
71static const int s_startupDefaultTimeout = 5;
74 : m_bounceSizesRatio(1.0)
75 , m_startupInfo(new KStartupInfo(KStartupInfo::CleanOnCantDetect, this))
76 , m_selection(nullptr)
80 , m_lastPresentTime(std::chrono::milliseconds::zero())
81 , m_type(BouncingFeedback)
83 , m_configWatcher(KConfigWatcher::create(KSharedConfig::openConfig(
"klaunchrc", KConfig::NoGlobals)))
84 , m_splashVisible(false)
87 if (KWindowSystem::isPlatformX11()) {
89 m_selection->claim(
true);
91 connect(m_startupInfo, &KStartupInfo::gotNewStartup,
this, [](
const KStartupInfoId &
id,
const KStartupInfoData &data) {
92 const auto icon = QIcon::fromTheme(data.findIcon(), QIcon::fromTheme(QStringLiteral(
"system-run")));
95 connect(m_startupInfo, &KStartupInfo::gotRemoveStartup,
this, [](
const KStartupInfoId &
id,
const KStartupInfoData &data) {
98 connect(m_startupInfo, &KStartupInfo::gotStartupChange,
this, [](
const KStartupInfoId &
id,
const KStartupInfoData &data) {
99 const auto icon = QIcon::fromTheme(data.findIcon(), QIcon::fromTheme(QStringLiteral(
"system-run")));
108 connect(m_configWatcher.data(), &KConfigWatcher::configChanged,
this, [
this]() {
109 reconfigure(ReconfigureAll);
113 m_splashVisible = QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral(
"org.kde.KSplash"));
114 auto serviceWatcher =
new QDBusServiceWatcher(QStringLiteral(
"org.kde.KSplash"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange,
this);
115 connect(serviceWatcher, &QDBusServiceWatcher::serviceRegistered,
this, [
this] {
116 m_splashVisible =
true;
119 connect(serviceWatcher, &QDBusServiceWatcher::serviceUnregistered,
this, [
this] {
120 m_splashVisible =
false;
121 gotRemoveStartup({});
139 KConfigGroup c = m_configWatcher->config()->group(QStringLiteral(
"FeedbackStyle"));
140 const bool busyCursor = c.readEntry(
"BusyCursor",
true);
142 c = m_configWatcher->config()->group(QStringLiteral(
"BusyCursorSettings"));
143 m_timeout = std::chrono::seconds(c.readEntry(
"Timeout", s_startupDefaultTimeout));
144 m_startupInfo->setTimeout(m_timeout.count());
145 const bool busyBlinking = c.readEntry(
"Blinking",
false);
146 const bool busyBouncing = c.readEntry(
"Bouncing",
true);
149 }
else if (busyBouncing) {
150 m_type = BouncingFeedback;
151 }
else if (busyBlinking) {
152 m_type = BlinkingFeedback;
156 if (m_blinkingShader->isValid()) {
157 qCDebug(KWIN_STARTUPFEEDBACK) <<
"Blinking Shader is valid";
159 qCDebug(KWIN_STARTUPFEEDBACK) <<
"Blinking Shader is not valid";
163 m_type = PassiveFeedback;
167 start(m_startups[m_currentStartup]);
174 if (m_lastPresentTime.count()) {
175 time = (presentTime - m_lastPresentTime).count();
177 m_lastPresentTime = presentTime;
185 case BouncingFeedback:
186 m_progress = (m_progress + time) % BOUNCE_DURATION;
187 m_frame = qRound((qreal)m_progress / (qreal)BOUNCE_FRAME_DURATION) % BOUNCE_FRAMES;
188 m_currentGeometry = feedbackRect();
189 data.
paint = data.
paint.united(m_currentGeometry);
191 case BlinkingFeedback:
192 m_progress = (m_progress + time) % BLINKING_DURATION;
193 m_frame = qRound((qreal)m_progress / (qreal)BLINKING_FRAME_DURATION) % BLINKING_FRAMES;
208 case BouncingFeedback:
209 texture = m_bouncingTextures[FRAME_TO_BOUNCE_TEXTURE[m_frame]].get();
211 case BlinkingFeedback:
212 case PassiveFeedback:
213 texture = m_texture.get();
222 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
224 if (m_type == BlinkingFeedback && m_blinkingShader && m_blinkingShader->isValid()) {
225 const QColor &blinkingColor = BLINKING_COLORS[FRAME_TO_BLINKING_COLOR[m_frame]];
227 shader = m_blinkingShader.get();
234 mvp.translate(pixelGeometry.x(), pixelGeometry.y());
237 texture->
render(pixelGeometry.size());
246 m_dirtyRect = m_currentGeometry;
247 if (m_type == BlinkingFeedback || m_type == BouncingFeedback) {
254void StartupFeedbackEffect::slotMouseChanged(
const QPointF &pos,
const QPointF &oldpos, Qt::MouseButtons buttons,
255 Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers)
258 m_dirtyRect |= m_currentGeometry;
259 m_currentGeometry = feedbackRect();
260 m_dirtyRect |= m_currentGeometry;
265void StartupFeedbackEffect::gotNewStartup(
const QString &
id,
const QIcon &icon)
267 Startup &startup = m_startups[id];
270 startup.expiredTimer = std::make_unique<QTimer>();
272 connect(startup.expiredTimer.get(), &QTimer::timeout,
this, [
this,
id]() {
273 gotRemoveStartup(id);
275 startup.expiredTimer->setSingleShot(
true);
276 startup.expiredTimer->start(m_timeout);
278 m_currentStartup = id;
282void StartupFeedbackEffect::gotRemoveStartup(
const QString &
id)
284 m_startups.remove(
id);
285 if (m_startups.isEmpty()) {
286 m_currentStartup.clear();
290 m_currentStartup = m_startups.begin().key();
291 start(m_startups[m_currentStartup]);
294void StartupFeedbackEffect::gotStartupChange(
const QString &
id,
const QIcon &icon)
296 if (m_currentStartup ==
id) {
297 Startup ¤tStartup = m_startups[m_currentStartup];
298 if (!icon.isNull() && icon.name() != currentStartup.icon.name()) {
299 currentStartup.icon = icon;
300 start(currentStartup);
305void StartupFeedbackEffect::start(
const Startup &startup)
323 m_cursorSize = mousecfg.readEntry(
"cursorSize", 24);
325 int iconSize = m_cursorSize / 1.5;
327 iconSize = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
330 if (m_type == BouncingFeedback) {
331 m_bounceSizesRatio = iconSize / 16.0;
334 const QPixmap iconPixmap = startup.icon.pixmap(iconSize);
335 prepareTextures(iconPixmap, output->scale());
336 m_dirtyRect = m_currentGeometry = feedbackRect();
340void StartupFeedbackEffect::stop()
346 m_lastPresentTime = std::chrono::milliseconds::zero();
349 case BouncingFeedback:
350 for (
int i = 0; i < 5; ++i) {
351 m_bouncingTextures[i].reset();
354 case BlinkingFeedback:
355 case PassiveFeedback:
366void StartupFeedbackEffect::prepareTextures(
const QPixmap &pix, qreal devicePixelRatio)
370 case BouncingFeedback:
371 for (
int i = 0; i < 5; ++i) {
372 m_bouncingTextures[i] =
GLTexture::upload(scalePixmap(pix, BOUNCE_SIZES[i], devicePixelRatio));
373 if (!m_bouncingTextures[i]) {
376 m_bouncingTextures[i]->setFilter(GL_LINEAR);
377 m_bouncingTextures[i]->setWrapMode(GL_CLAMP_TO_EDGE);
380 case BlinkingFeedback:
381 case PassiveFeedback:
386 m_texture->setFilter(GL_LINEAR);
387 m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
392 m_lastPresentTime = std::chrono::milliseconds::zero();
397QImage StartupFeedbackEffect::scalePixmap(
const QPixmap &pm,
const QSize &size, qreal devicePixelRatio)
const
399 const QSize &adjustedSize = size * m_bounceSizesRatio;
400 QImage scaled = pm.toImage().scaled(adjustedSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
401 if (scaled.format() != QImage::Format_ARGB32_Premultiplied && scaled.format() != QImage::Format_ARGB32) {
402 scaled.convertTo(QImage::Format_ARGB32);
405 QImage result(feedbackIconSize() * devicePixelRatio, QImage::Format_ARGB32);
406 result.setDevicePixelRatio(devicePixelRatio);
409 p.setCompositionMode(QPainter::CompositionMode_Source);
410 p.fillRect(result.rect(), Qt::transparent);
411 p.drawImage(QRectF((20 * m_bounceSizesRatio - adjustedSize.width()) / 2,
412 (20 * m_bounceSizesRatio - adjustedSize.height()) / 2,
413 adjustedSize.width(),
414 adjustedSize.height()),
419QSize StartupFeedbackEffect::feedbackIconSize()
const
421 return QSize(20, 20) * m_bounceSizesRatio;
424QRect StartupFeedbackEffect::feedbackRect()
const
427 if (m_cursorSize <= 16) {
429 }
else if (m_cursorSize <= 32) {
431 }
else if (m_cursorSize <= 48) {
437 GLTexture *texture =
nullptr;
440 case BouncingFeedback:
441 texture = m_bouncingTextures[FRAME_TO_BOUNCE_TEXTURE[m_frame]].get();
442 yOffset = FRAME_TO_BOUNCE_YOFFSET[m_frame] * m_bounceSizesRatio;
444 case BlinkingFeedback:
445 case PassiveFeedback:
446 texture = m_texture.get();
455 rect = QRect(
cursorPos, feedbackIconSize());
467#include "moc_startupfeedback.cpp"
void startupRemoved(const QString &id)
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, Output *screen)
void startupAdded(const QString &id, const QIcon &icon)
Q_SCRIPTABLE void addRepaint(const QRectF &r)
bool isCursorHidden() const
bool makeOpenGLContextCurrent()
Makes the OpenGL compositing context current.
CompositingType compositingType
Output * screenAt(const QPoint &point) const
KSharedConfigPtr inputConfig() const
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
bool isOpenGLCompositing() const
Whether the Compositor is OpenGL based (either GL 1 or 2).
xcb_connection_t * xcbConnection() const
void mouseChanged(const QPointF &pos, const QPointF &oldpos, Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers)
xcb_window_t x11RootWindow() const
void startupChanged(const QString &id, const QIcon &icon)
Q_SCRIPTABLE void addRepaintFull()
bool setColorspaceUniformsFromSRGB(const ColorDescription &dst)
@ ModelViewProjectionMatrix
bool setUniform(const char *name, float value)
static std::unique_ptr< GLTexture > upload(const QImage &image)
void render(const QSizeF &size)
const ColorDescription & colorDescription() const
QMatrix4x4 projectionMatrix() const
std::unique_ptr< GLShader > generateShaderFromFile(ShaderTraits traits, const QString &vertexFile=QString(), const QString &fragmentFile=QString())
static ShaderManager * instance()
GLShader * pushShader(ShaderTraits traits)
bool isActive() const override
~StartupFeedbackEffect() override
void postPaintScreen() override
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, Output *screen) override
void reconfigure(ReconfigureFlags flags) override
static QPointF cursorPos()
KWIN_EXPORT QPointF snapToPixelGridF(const QPointF &point)
KWIN_EXPORT QRectF scaledRect(const QRectF &rect, qreal scale)