diff --git a/abstract_client.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -265,6 +265,14 @@ */ 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 @@ */ void showApplicationMenu(int actionId); + bool unresponsive() const; + public Q_SLOTS: virtual void closeWindow() = 0; @@ -694,6 +704,7 @@ void desktopFileNameChanged(); void hasApplicationMenuChanged(bool); void applicationMenuActiveChanged(bool); + void unresponsiveChanged(bool); protected: AbstractClient(); @@ -978,6 +989,8 @@ void updateApplicationMenuServiceName(const QString &serviceName); void updateApplicationMenuObjectPath(const QString &objectPath); + void setUnresponsive(bool unresponsive); + private: void handlePaletteChange(); QSharedPointer m_tabBoxClient; @@ -1050,6 +1063,8 @@ QString m_applicationMenuServiceName; QString m_applicationMenuObjectPath; + bool m_unresponsive = false; + static bool s_haveResizeEffect; }; diff --git a/abstract_client.cpp b/abstract_client.cpp --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1735,4 +1735,17 @@ } } +bool AbstractClient::unresponsive() const +{ + return m_unresponsive; +} + +void AbstractClient::setUnresponsive(bool unresponsive) +{ + if (m_unresponsive != unresponsive) { + m_unresponsive = unresponsive; + emit unresponsiveChanged(m_unresponsive); + } +} + } diff --git a/client.cpp b/client.cpp --- a/client.cpp +++ b/client.cpp @@ -1124,14 +1124,24 @@ 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 +1153,9 @@ 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; diff --git a/decorations/decoratedclient.cpp b/decorations/decoratedclient.cpp --- a/decorations/decoratedclient.cpp +++ b/decorations/decoratedclient.cpp @@ -30,6 +30,8 @@ #include #include +#include + #include namespace KWin @@ -113,6 +115,10 @@ connect(client, &AbstractClient::hasApplicationMenuChanged, decoratedClient, &KDecoration2::DecoratedClient::hasApplicationMenuChanged); connect(client, &AbstractClient::applicationMenuActiveChanged, decoratedClient, &KDecoration2::DecoratedClient::applicationMenuActiveChanged); + + connect(client, &AbstractClient::unresponsiveChanged, decoratedClient, [this, decoratedClient]() { + emit decoratedClient->captionChanged(decoratedClient->caption()); + }); } DecoratedClientImpl::~DecoratedClientImpl() = default; @@ -129,7 +135,14 @@ #define DELEGATE2(type, name) DELEGATE(type, name, name) -DELEGATE2(QString, caption) +QString DecoratedClientImpl::caption() const +{ + QString caption = m_client->caption(); + if (m_client->unresponsive()) { + caption += i18nc("Application is not responding, appended to window title", " (Not Responding)"); + } + return caption; +} DELEGATE2(bool, isActive) DELEGATE2(bool, isCloseable) DELEGATE(bool, isMaximizeable, isMaximizable) diff --git a/effects.h b/effects.h --- a/effects.h +++ b/effects.h @@ -272,6 +272,7 @@ void slotClientMaximized(KWin::AbstractClient *c, MaximizeMode maxMode); void slotOpacityChanged(KWin::Toplevel *t, qreal oldOpacity); void slotClientModalityChanged(); + void slotClientUnresponsiveChanged(bool unresponsive); void slotGeometryShapeChanged(KWin::Toplevel *t, const QRect &old); void slotPaddingChanged(KWin::Toplevel *t, const QRect &old); void slotWindowDamaged(KWin::Toplevel *t, const QRect& r); diff --git a/effects.cpp b/effects.cpp --- a/effects.cpp +++ b/effects.cpp @@ -282,6 +282,7 @@ } ); connect(c, &AbstractClient::modalChanged, this, &EffectsHandlerImpl::slotClientModalityChanged); + connect(c, &AbstractClient::unresponsiveChanged, this, &EffectsHandlerImpl::slotClientUnresponsiveChanged); connect(c, &AbstractClient::geometryShapeChanged, this, &EffectsHandlerImpl::slotGeometryShapeChanged); connect(c, &AbstractClient::damaged, this, &EffectsHandlerImpl::slotWindowDamaged); connect(c, &AbstractClient::windowShown, this, @@ -534,6 +535,11 @@ emit windowModalityChanged(static_cast(sender())->effectWindow()); } +void EffectsHandlerImpl::slotClientUnresponsiveChanged(bool unresponsive) +{ + emit windowUnresponsiveChanged(static_cast(sender())->effectWindow(), unresponsive); +} + void EffectsHandlerImpl::slotCurrentTabAboutToChange(EffectWindow *from, EffectWindow *to) { emit currentTabAboutToChange(from, to); diff --git a/effects/CMakeLists.txt b/effects/CMakeLists.txt --- a/effects/CMakeLists.txt +++ b/effects/CMakeLists.txt @@ -132,6 +132,7 @@ add_subdirectory( eyeonscreen ) add_subdirectory( fade ) add_subdirectory( fadedesktop ) +add_subdirectory( frozenapp ) add_subdirectory( login ) add_subdirectory( logout ) add_subdirectory( maximize ) diff --git a/effects/frozenapp/CMakeLists.txt b/effects/frozenapp/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/effects/frozenapp/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(package) diff --git a/effects/frozenapp/package/CMakeLists.txt b/effects/frozenapp/package/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/effects/frozenapp/package/CMakeLists.txt @@ -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) diff --git a/effects/frozenapp/package/contents/code/main.js b/effects/frozenapp/package/contents/code/main.js new file mode 100644 --- /dev/null +++ b/effects/frozenapp/package/contents/code/main.js @@ -0,0 +1,92 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + + Copyright (C) 2017 Kai Uwe Broulik + +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 . +*********************************************************************/ +/*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); + }, + unresponsiveChanged: function (window, unresponsive) { + "use strict"; + + if (unresponsive) { + frozenAppEffect.appFroze(window); + } else { + frozenAppEffect.appUnfroze(window); + } + }, + appFroze: function (window) { + "use strict"; + + if (window.pingAnimation) { + return; + } + + window.pingAnimation = set({ + window: window, + duration: frozenAppEffect.inDuration, + animations: [{ + type: Effect.Saturation, + to: 0.1 + }, { + type: Effect.Brightness, + to: 1.25 + }] + }); + }, + appUnfroze: function (window) { + "use strict"; + + if (!window.pingAnimation) { + return; + } + + cancel(window.pingAnimation); + window.pingAnimation = undefined; + + animate({ + window: window, + duration: frozenAppEffect.outDuration, + animations: [{ + type: Effect.Saturation, + from: 0.1, + to: 1.0 + }, { + type: Effect.Brightness, + from: 1.25, + to: 1.0 + }] + }); + }, + windowClosed: function (window) { + "use strict"; + frozenAppEffect.appUnfroze(window); + }, + init: function () { + "use strict"; + effect.configChanged.connect(frozenAppEffect.loadConfig); + effects.windowUnresponsiveChanged.connect(frozenAppEffect.unresponsiveChanged); + effects.windowClosed.connect(frozenAppEffect.windowClosed); + } +}; +frozenAppEffect.init(); diff --git a/effects/frozenapp/package/metadata.desktop b/effects/frozenapp/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/effects/frozenapp/package/metadata.desktop @@ -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 diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -1443,6 +1443,14 @@ **/ 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. * @param w The window which is scheduled for repainting