Desaturate non-responsive windows

When an application is not responding, its window is desaturated to communicate this.
Also "(Not Responding)" is added to the title bar.

Differential Revision: https://phabricator.kde.org/D5245
This commit is contained in:
Kai Uwe Broulik 2017-04-05 11:16:23 +02:00
parent 5d044331fc
commit 1eb950a985
12 changed files with 259 additions and 6 deletions

View file

@ -1735,4 +1735,18 @@ void AbstractClient::showApplicationMenu(int actionId)
}
}
bool AbstractClient::unresponsive() const
{
return m_unresponsive;
}
void AbstractClient::setUnresponsive(bool unresponsive)
{
if (m_unresponsive != unresponsive) {
m_unresponsive = unresponsive;
emit unresponsiveChanged(m_unresponsive);
emit captionChanged();
}
}
}

View file

@ -265,6 +265,14 @@ class KWIN_EXPORT AbstractClient : public Toplevel
*/
Q_PROPERTY(bool applicationMenuActive READ applicationMenuActive NOTIFY applicationMenuActiveChanged)
/**
* Whether this client is unresponsive.
*
* When an application failed to react on a ping request in time, it is
* considered unresponsive. This usually indicates that the application froze or crashed.
*/
Q_PROPERTY(bool unresponsive READ unresponsive NOTIFY unresponsiveChanged)
public:
virtual ~AbstractClient();
@ -653,6 +661,8 @@ public:
*/
void showApplicationMenu(int actionId);
bool unresponsive() const;
public Q_SLOTS:
virtual void closeWindow() = 0;
@ -694,6 +704,7 @@ Q_SIGNALS:
void desktopFileNameChanged();
void hasApplicationMenuChanged(bool);
void applicationMenuActiveChanged(bool);
void unresponsiveChanged(bool);
protected:
AbstractClient();
@ -978,6 +989,8 @@ protected:
void updateApplicationMenuServiceName(const QString &serviceName);
void updateApplicationMenuObjectPath(const QString &objectPath);
void setUnresponsive(bool unresponsive);
private:
void handlePaletteChange();
QSharedPointer<TabBox::TabBoxClientImpl> m_tabBoxClient;
@ -1050,6 +1063,8 @@ private:
QString m_applicationMenuServiceName;
QString m_applicationMenuObjectPath;
bool m_unresponsive = false;
static bool s_haveResizeEffect;
};

View file

@ -133,6 +133,7 @@ void TestScriptedEffectLoader::testHasEffect_data()
QTest::newRow("Fade + kwin4_effect") << QStringLiteral("kwin4_effect_fade") << true;
QTest::newRow("Fade + kwin4_effect + CS") << QStringLiteral("kwin4_eFfect_fAde") << true;
QTest::newRow("FadeDesktop") << QStringLiteral("kwin4_effect_fadedesktop") << true;
QTest::newRow("FrozenApp") << QStringLiteral("kwin4_effect_frozenapp") << true;
QTest::newRow("DialogParent") << QStringLiteral("kwin4_effect_dialogparent") << true;
QTest::newRow("Login") << QStringLiteral("kwin4_effect_login") << true;
QTest::newRow("Logout") << QStringLiteral("kwin4_effect_logout") << true;
@ -165,6 +166,7 @@ void TestScriptedEffectLoader::testKnownEffects()
expectedEffects << QStringLiteral("kwin4_effect_dialogparent")
<< QStringLiteral("kwin4_effect_fade")
<< QStringLiteral("kwin4_effect_fadedesktop")
<< QStringLiteral("kwin4_effect_frozenapp")
<< QStringLiteral("kwin4_effect_login")
<< QStringLiteral("kwin4_effect_logout")
<< QStringLiteral("kwin4_effect_maximize")
@ -190,6 +192,7 @@ void TestScriptedEffectLoader::testLoadEffect_data()
QTest::newRow("Fade + kwin4_effect") << QStringLiteral("kwin4_effect_fade") << true;
QTest::newRow("Fade + kwin4_effect + CS") << QStringLiteral("kwin4_eFfect_fAde") << true;
QTest::newRow("FadeDesktop") << QStringLiteral("kwin4_effect_fadedesktop") << true;
QTest::newRow("FrozenApp") << QStringLiteral("kwin4_effect_frozenapp") << true;
QTest::newRow("DialogParent") << QStringLiteral("kwin4_effect_dialogparent") << true;
QTest::newRow("Login") << QStringLiteral("kwin4_effect_login") << true;
QTest::newRow("Logout") << QStringLiteral("kwin4_effect_logout") << true;
@ -342,6 +345,7 @@ void TestScriptedEffectLoader::testLoadAllEffects()
plugins.writeEntry(kwin4 + QStringLiteral("dialogparentEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("fadeEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("fadedesktopEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("frozenappEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("loginEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("logoutEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("maximizeEnabled"), false);

View file

@ -39,6 +39,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KDecoration2/Decoration>
#include <KDecoration2/DecoratedClient>
// KDE
#include <KLocalizedString>
#include <KWindowSystem>
#include <KColorScheme>
// Qt
@ -1124,14 +1125,24 @@ void Client::pingWindow()
ping_timer = new QTimer(this);
connect(ping_timer, &QTimer::timeout, this,
[this]() {
qCDebug(KWIN_CORE) << "Ping timeout:" << caption();
ping_timer->deleteLater();
ping_timer = nullptr;
killProcess(true, m_pingTimestamp);
if (unresponsive()) {
qCDebug(KWIN_CORE) << "Final ping timeout, asking to kill:" << caption();
ping_timer->deleteLater();
ping_timer = nullptr;
killProcess(true, m_pingTimestamp);
return;
}
qCDebug(KWIN_CORE) << "First ping timeout:" << caption();
setUnresponsive(true);
ping_timer->start();
}
);
ping_timer->setSingleShot(true);
ping_timer->start(options->killPingTimeout());
// we'll run the timer twice, at first we'll desaturate the window
// and the second time we'll show the "do you want to kill" prompt
ping_timer->start(options->killPingTimeout() / 2);
m_pingTimestamp = xTime();
workspace()->sendPingToWindow(window(), m_pingTimestamp);
}
@ -1143,6 +1154,9 @@ void Client::gotPing(xcb_timestamp_t timestamp)
return;
delete ping_timer;
ping_timer = NULL;
setUnresponsive(false);
if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
::kill(m_killHelperPID, SIGTERM);
m_killHelperPID = 0;
@ -1536,8 +1550,13 @@ void Client::fetchIconicName()
QString Client::caption(bool full, bool stripped) const
{
QString cap = stripped ? cap_deco : cap_normal;
if (full)
if (full) {
cap += cap_suffix;
if (unresponsive()) {
cap += QLatin1String(" ");
cap += i18nc("Application is not responding, appended to window title", "(Not Responding)");
}
}
return cap;
}

View file

@ -284,6 +284,11 @@ void EffectsHandlerImpl::setupAbstractClientConnections(AbstractClient* c)
connect(c, &AbstractClient::modalChanged, this, &EffectsHandlerImpl::slotClientModalityChanged);
connect(c, &AbstractClient::geometryShapeChanged, this, &EffectsHandlerImpl::slotGeometryShapeChanged);
connect(c, &AbstractClient::damaged, this, &EffectsHandlerImpl::slotWindowDamaged);
connect(c, &AbstractClient::unresponsiveChanged, this,
[this, c](bool unresponsive) {
emit windowUnresponsiveChanged(c->effectWindow(), unresponsive);
}
);
connect(c, &AbstractClient::windowShown, this,
[this](Toplevel *c) {
emit windowShown(c->effectWindow());

View file

@ -132,6 +132,7 @@ add_subdirectory( dialogparent )
add_subdirectory( eyeonscreen )
add_subdirectory( fade )
add_subdirectory( fadedesktop )
add_subdirectory( frozenapp )
add_subdirectory( login )
add_subdirectory( logout )
add_subdirectory( maximize )

View file

@ -0,0 +1 @@
add_subdirectory(package)

View file

@ -0,0 +1,6 @@
install(DIRECTORY contents DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME}/effects/kwin4_effect_frozenapp)
install(FILES metadata.desktop DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME}/effects/kwin4_effect_frozenapp)
install(FILES metadata.desktop
DESTINATION ${SERVICES_INSTALL_DIR}/${KWIN_NAME}
RENAME kwin4_effect_frozenapp.desktop)

View file

@ -0,0 +1,142 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2017 Kai Uwe Broulik <kde@privat.broulik.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
/*global effect, effects, animate, animationTime, Effect*/
var frozenAppEffect = {
inDuration: animationTime(1500),
outDuration: animationTime(250),
loadConfig: function () {
"use strict";
frozenAppEffect.inDuration = animationTime(1500);
frozenAppEffect.outDuration = animationTime(250);
},
windowAdded: function (window) {
"use strict";
if (!window || !window.unresponsive) {
return;
}
frozenAppEffect.windowBecameUnresponsive(window);
},
windowBecameUnresponsive: function (window) {
"use strict";
if (window.unresponsiveAnimation) {
return;
}
frozenAppEffect.startAnimation(window, frozenAppEffect.inDuration);
},
startAnimation: function (window, duration) {
"use strict";
if (!window.visible) {
return;
}
window.unresponsiveAnimation = set({
window: window,
duration: duration,
animations: [{
type: Effect.Saturation,
to: 0.1
}, {
type: Effect.Brightness,
to: 1.5
}]
});
},
windowClosed: function (window) {
"use strict";
frozenAppEffect.cancelAnimation(window);
if (!window.unresponsive) {
return;
}
frozenAppEffect.windowBecameResponsive(window);
},
windowBecameResponsive: function (window) {
"use strict";
if (!window.unresponsiveAnimation) {
return;
}
cancel(window.unresponsiveAnimation);
window.unresponsiveAnimation = undefined;
animate({
window: window,
duration: frozenAppEffect.outDuration,
animations: [{
type: Effect.Saturation,
from: 0.1,
to: 1.0
}, {
type: Effect.Brightness,
from: 1.5,
to: 1.0
}]
});
},
cancelAnimation: function (window) {
"use strict";
if (window.unresponsiveAnimation) {
print(window);
cancel(window.unresponsiveAnimation);
window.unresponsiveAnimation = undefined;
}
},
desktopChanged: function () {
"use strict";
var windows = effects.stackingOrder;
for (var i = 0, length = windows.length; i < length; ++i) {
print(i);
var window = windows[i];
frozenAppEffect.cancelAnimation(window);
frozenAppEffect.restartAnimation(window);
}
},
unresponsiveChanged: function (window) {
"use strict";
if (window.unresponsive) {
frozenAppEffect.windowBecameUnresponsive(window);
} else {
frozenAppEffect.windowBecameResponsive(window);
}
},
restartAnimation: function (window) {
"use strict";
if (!window || !window.unresponsive) {
return;
}
frozenAppEffect.startAnimation(window, 1);
},
init: function () {
"use strict";
effects.windowAdded.connect(frozenAppEffect.windowAdded);
effects.windowClosed.connect(frozenAppEffect.windowClosed);
effects.windowMinimized.connect(frozenAppEffect.cancelAnimation);
effects.windowUnminimized.connect(frozenAppEffect.restartAnimation);
effects.windowUnresponsiveChanged.connect(frozenAppEffect.unresponsiveChanged);
effects['desktopChanged(int,int)'].connect(frozenAppEffect.desktopChanged);
effects.desktopPresenceChanged.connect(frozenAppEffect.cancelAnimation);
effects.desktopPresenceChanged.connect(frozenAppEffect.restartAnimation);
var windows = effects.stackingOrder;
for (var i = 0, length = windows.length; i < length; ++i) {
frozenAppEffect.restartAnimation(windows[i]);
}
}
};
frozenAppEffect.init();

View file

@ -0,0 +1,22 @@
[Desktop Entry]
Name=Desaturate Unresponsive Applications
Icon=preferences-system-windows-effect-frozenapp
Comment=Desaturate windows of unresponsive (frozen) applications
Type=Service
X-KDE-ServiceTypes=KWin/Effect,KCModule
X-KDE-PluginInfo-Author=Kai Uwe Broulik
X-KDE-PluginInfo-Email=kde@privat.broulik.de
X-KDE-PluginInfo-Name=kwin4_effect_frozenapp
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Category=Appearance
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-Ordering=60
X-Plasma-API=javascript
X-Plasma-MainScript=code/main.js
X-KDE-PluginKeyword=kwin4_effect_frozenapp
X-KDE-Library=kcm_kwin4_genericscripted
X-KDE-ParentComponents=kwin4_effect_frozen
X-KWin-Config-TranslationDomain=kwin_effects

View file

@ -873,6 +873,7 @@ WINDOW_HELPER_DEFAULT(bool, isSkipSwitcher, "skipSwitcher", false)
WINDOW_HELPER_DEFAULT(bool, isCurrentTab, "isCurrentTab", true)
WINDOW_HELPER_DEFAULT(bool, decorationHasAlpha, "decorationHasAlpha", false)
WINDOW_HELPER_DEFAULT(bool, isFullScreen, "fullScreen", false)
WINDOW_HELPER_DEFAULT(bool, isUnresponsive, "unresponsive", false)
#undef WINDOW_HELPER_DEFAULT

View file

@ -1442,6 +1442,14 @@ Q_SIGNALS:
* @since 4.11
**/
void windowModalityChanged(KWin::EffectWindow *w);
/**
* Signal emitted when a window either became unresponsive (eg. app froze or crashed)
* or respoonsive
* @param w The window that became (un)responsive
* @param unresponsive Whether the window is responsive or unresponsive
* @since 5.10
*/
void windowUnresponsiveChanged(KWin::EffectWindow *w, bool unresponsive);
/**
* Signal emitted when an area of a window is scheduled for repainting.
* Use this signal in an effect if another area needs to be synced as well.
@ -1867,6 +1875,16 @@ class KWINEFFECTS_EXPORT EffectWindow : public QObject
* @since 5.6
**/
Q_PROPERTY(bool fullScreen READ isFullScreen)
/**
* Whether this client is unresponsive.
*
* When an application failed to react on a ping request in time, it is
* considered unresponsive. This usually indicates that the application froze or crashed.
*
* @since 5.10
*/
Q_PROPERTY(bool unresponsive READ isUnresponsive)
public:
/** Flags explaining why painting should be disabled */
enum {
@ -2109,6 +2127,11 @@ public:
**/
bool isFullScreen() const;
/**
* @since 5.10
*/
bool isUnresponsive() const;
/**
* Can be used to by effects to store arbitrary data in the EffectWindow.
*