kwin/effects/highlightwindow/highlightwindow.cpp
Vlad Zahorodnii 4dcb3c495c effects/highlightwindows: Re-implement as AnimationEffect
On Wayland, the highlight window effect is not guaranteed to work all
the time because it uses the opacity to determine if the animation has
completed.

This change rewrites the highlight window effect using AnimationEffect
API to make it work both on X11 and Wayland. The implementation is based
on how the translucency effect works.
2021-02-01 13:07:11 +00:00

249 lines
7.7 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2009 Lucas Murray <lmurray@undefinedfire.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "highlightwindow.h"
namespace KWin
{
HighlightWindowEffect::HighlightWindowEffect()
: m_easingCurve(QEasingCurve::Linear)
, m_fadeDuration(animationTime(150))
, m_monitorWindow(nullptr)
{
m_atom = effects->announceSupportProperty("_KDE_WINDOW_HIGHLIGHT", this);
connect(effects, &EffectsHandler::windowAdded, this, &HighlightWindowEffect::slotWindowAdded);
connect(effects, &EffectsHandler::windowClosed, this, &HighlightWindowEffect::slotWindowClosed);
connect(effects, &EffectsHandler::windowDeleted, this, &HighlightWindowEffect::slotWindowDeleted);
connect(effects, &EffectsHandler::propertyNotify, this,
[this](EffectWindow *w, long atom) {
slotPropertyNotify(w, atom, nullptr);
}
);
connect(effects, &EffectsHandler::xcbConnectionChanged, this,
[this] {
m_atom = effects->announceSupportProperty("_KDE_WINDOW_HIGHLIGHT", this);
}
);
}
static bool isInitiallyHidden(EffectWindow* w)
{
// Is the window initially hidden until it is highlighted?
return w->isMinimized() || !w->isOnCurrentDesktop();
}
static bool isHighlightWindow(EffectWindow *window)
{
return window->isNormalWindow() || window->isDialog();
}
void HighlightWindowEffect::slotWindowAdded(EffectWindow* w)
{
if (!m_highlightedWindows.isEmpty()) {
// On X11, the tabbox may ask us to highlight itself before the windowAdded signal
// is emitted because override-redirect windows are shown after synthetic 50ms delay.
if (m_highlightedWindows.contains(w)) {
return;
}
// This window was demanded to be highlighted before it appeared on the screen.
for (const WId &id : qAsConst(m_highlightedIds)) {
if (w == effects->findWindow(id)) {
startHighlightAnimation(w, 0);
return;
}
}
if (isHighlightWindow(w)) {
startGhostAnimation(w, 0); // this window is not currently highlighted
}
}
slotPropertyNotify(w, m_atom, w); // Check initial value
}
void HighlightWindowEffect::slotWindowClosed(EffectWindow* w)
{
if (m_monitorWindow == w) // The monitoring window was destroyed
finishHighlighting();
}
void HighlightWindowEffect::slotWindowDeleted(EffectWindow* w)
{
m_animations.remove(w);
}
void HighlightWindowEffect::slotPropertyNotify(EffectWindow* w, long a, EffectWindow *addedWindow)
{
if (a != m_atom || m_atom == XCB_ATOM_NONE)
return; // Not our atom
// if the window is null, the property was set on the root window - see events.cpp
QByteArray byteData = w ? w->readProperty(m_atom, m_atom, 32) :
effects->readRootProperty(m_atom, m_atom, 32);
if (byteData.length() < 1) {
// Property was removed, clearing highlight
if (!addedWindow || w != addedWindow)
finishHighlighting();
return;
}
auto* data = reinterpret_cast<uint32_t*>(byteData.data());
if (!data[0]) {
// Purposely clearing highlight by issuing a NULL target
finishHighlighting();
return;
}
m_monitorWindow = w;
bool found = false;
int length = byteData.length() / sizeof(data[0]);
//foreach ( EffectWindow* e, m_highlightedWindows )
// effects->setElevatedWindow( e, false );
m_highlightedWindows.clear();
m_highlightedIds.clear();
for (int i = 0; i < length; i++) {
m_highlightedIds << data[i];
EffectWindow* foundWin = effects->findWindow(data[i]);
if (!foundWin) {
qCDebug(KWINEFFECTS) << "Invalid window targetted for highlight. Requested:" << data[i];
continue; // might come in later.
}
m_highlightedWindows.append(foundWin);
// TODO: We cannot just simply elevate the window as this will elevate it over
// Plasma tooltips and other such windows as well
//effects->setElevatedWindow( foundWin, true );
found = true;
}
if (!found) {
finishHighlighting();
return;
}
prepareHighlighting();
}
void HighlightWindowEffect::prepareHighlighting()
{
const EffectWindowList windows = effects->stackingOrder();
for (EffectWindow *window : windows) {
if (!isHighlightWindow(window)) {
continue;
}
if (isHighlighted(window)) {
startHighlightAnimation(window);
} else {
startGhostAnimation(window);
}
}
}
void HighlightWindowEffect::finishHighlighting()
{
const EffectWindowList windows = effects->stackingOrder();
for (EffectWindow *window : windows) {
if (isHighlightWindow(window)) {
startRevertAnimation(window);
}
}
// Sanity check, ideally, this should never happen.
if (!m_animations.isEmpty()) {
for (quint64 &animationId : m_animations) {
cancel(animationId);
}
m_animations.clear();
}
m_monitorWindow = nullptr;
m_highlightedWindows.clear();
}
void HighlightWindowEffect::highlightWindows(const QVector<KWin::EffectWindow *> &windows)
{
if (windows.isEmpty()) {
finishHighlighting();
return;
}
m_monitorWindow = nullptr;
m_highlightedWindows.clear();
m_highlightedIds.clear();
for (auto w : windows) {
m_highlightedWindows << w;
}
prepareHighlighting();
}
void HighlightWindowEffect::startGhostAnimation(EffectWindow *window, int duration)
{
if (duration == -1) {
duration = m_fadeDuration;
}
quint64 &animationId = m_animations[window];
if (animationId) {
retarget(animationId, FPx2(m_ghostOpacity, m_ghostOpacity), m_fadeDuration);
} else {
const qreal startOpacity = isInitiallyHidden(window) ? 0 : 1;
animationId = set(window, Opacity, 0, m_fadeDuration, FPx2(m_ghostOpacity, m_ghostOpacity),
m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
}
}
void HighlightWindowEffect::startHighlightAnimation(EffectWindow *window, int duration)
{
if (duration == -1) {
duration = m_fadeDuration;
}
quint64 &animationId = m_animations[window];
if (animationId) {
retarget(animationId, FPx2(1.0, 1.0), m_fadeDuration);
} else {
const qreal startOpacity = isInitiallyHidden(window) ? 0 : 1;
animationId = set(window, Opacity, 0, m_fadeDuration, FPx2(1.0, 1.0),
m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
}
}
void HighlightWindowEffect::startRevertAnimation(EffectWindow *window)
{
const quint64 animationId = m_animations.take(window);
if (animationId) {
const qreal startOpacity = isHighlighted(window) ? 1 : m_ghostOpacity;
const qreal endOpacity = isInitiallyHidden(window) ? 0 : 1;
animate(window, Opacity, 0, m_fadeDuration, FPx2(endOpacity, endOpacity),
m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
cancel(animationId);
}
}
bool HighlightWindowEffect::isHighlighted(EffectWindow *window) const
{
return m_highlightedWindows.contains(window);
}
bool HighlightWindowEffect::provides(Feature feature)
{
switch (feature) {
case HighlightWindows:
return true;
default:
return false;
}
}
bool HighlightWindowEffect::perform(Feature feature, const QVariantList &arguments)
{
if (feature != HighlightWindows) {
return false;
}
if (arguments.size() != 1) {
return false;
}
highlightWindows(arguments.first().value<QVector<EffectWindow*>>());
return true;
}
} // namespace