qt/quickterm: real per-window fade via wp_alpha_modifier_v1
QtWayland's QPA plugin has no implementation for
QWindow::setOpacity — every call spams
"This plugin does not support setting window opacity" to stderr,
and the QuickTerminal's QPropertyAnimation on windowOpacity
fired it once per animation tick (~10-50 times per fade).
Replace the QPropertyAnimation + setWindowOpacity path with the
wp_alpha_modifier_v1 staging Wayland protocol, which lets the
compositor multiply per-surface alpha directly. Supported by
KWin (KDE 6+), wlroots ≥0.17, Hyprland; not yet on mutter/GNOME.
For non-supporting compositors the AlphaModifier::setOpacity
call returns false and the animation visibly does nothing —
acceptable degradation (window still shows/hides, just without
the fade) versus throwing the warning storm.
Pieces:
- qt/protocols/alpha-modifier-v1.xml — vendor the upstream
wayland-protocols staging XML.
- qt/CMakeLists.txt — wire it through the existing
`ghastty_wayland_protocol(...)` helper.
- qt/src/wayland/AlphaModifier.{h,cpp} — process-wide manager
binding (lazy init, std::call_once), per-wl_surface cache so
animation ticks don't re-roundtrip get_surface, set_multiplier
+ wl_surface.commit + wl_display_flush per call. Migrates the
bound manager onto the default queue before destroying the
private registry queue (same gotcha that produced the exit-
time SIGSEGV in XkbTracker — caught it preemptively here).
- qt/src/quickterm/QuickTerminal.cpp — animateIn/animateOut now
drive a QVariantAnimation whose valueChanged routes through
AlphaModifier::setOpacity, instead of a QPropertyAnimation on
the windowOpacity property.
Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
parent
c1a55b2576
commit
5a24a90f4e
|
|
@ -108,6 +108,14 @@ ghastty_wayland_protocol(blur BLUR_HEADER BLUR_CODE)
|
|||
ghastty_wayland_protocol(linux-dmabuf-v1 DMABUF_HEADER DMABUF_CODE)
|
||||
ghastty_wayland_protocol(viewporter VIEWPORTER_HEADER VIEWPORTER_CODE)
|
||||
ghastty_wayland_protocol(fractional-scale-v1 FRACSCALE_HEADER FRACSCALE_CODE)
|
||||
# - `alpha-modifier-v1` (`wp_alpha_modifier_v1`)
|
||||
# — compositor-side per-surface alpha multiplier. QtWayland has no
|
||||
# built-in setWindowOpacity equivalent (the QPA plugin warns
|
||||
# "This plugin does not support setting window opacity" on every
|
||||
# call), so QuickTerminal's fade-in/out drives this protocol
|
||||
# directly. Supported on KWin, wlroots ≥0.17, Hyprland; NOT yet
|
||||
# on mutter/GNOME.
|
||||
ghastty_wayland_protocol(alpha-modifier-v1 ALPHAMOD_HEADER ALPHAMOD_CODE)
|
||||
|
||||
# libghostty is built out-of-tree by Zig.
|
||||
get_filename_component(GHOSTTY_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
|
@ -199,6 +207,7 @@ add_executable(ghastty
|
|||
src/TabWidget.cpp
|
||||
src/undo/UndoStack.cpp
|
||||
src/Util.cpp
|
||||
src/wayland/AlphaModifier.cpp
|
||||
src/wayland/SubsurfacePresenter.cpp
|
||||
src/WindowBlur.cpp
|
||||
src/XkbTracker.cpp
|
||||
|
|
@ -210,6 +219,8 @@ add_executable(ghastty
|
|||
"${VIEWPORTER_HEADER}"
|
||||
"${FRACSCALE_CODE}"
|
||||
"${FRACSCALE_HEADER}"
|
||||
"${ALPHAMOD_CODE}"
|
||||
"${ALPHAMOD_HEADER}"
|
||||
)
|
||||
|
||||
# Vulkan host glue is variant-only. Adding it to the OpenGL build
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="alpha_modifier_v1">
|
||||
<copyright>
|
||||
Copyright 2023 Xaver Hugl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<description summary="surface alpha modifier">
|
||||
This interface allows a client to set a factor for the alpha values on a
|
||||
surface, which can be used to offload such operations to the compositor,
|
||||
which can in turn for example offload them to KMS.
|
||||
|
||||
Warning! The protocol described in this file is currently in the testing
|
||||
phase. Backward compatible changes may be added together with the
|
||||
corresponding interface version bump. Backward incompatible changes can
|
||||
only be done by creating a new major version of the extension.
|
||||
</description>
|
||||
|
||||
<interface name="wp_alpha_modifier_v1" version="1">
|
||||
<description summary="surface alpha modifier manager">
|
||||
This interface allows a client to set a factor for the alpha values on
|
||||
a surface, which can be used to offload such operations to the
|
||||
compositor. The default factor is UINT32_MAX.
|
||||
|
||||
This interface can be used to set an arbitrary alpha value for the
|
||||
surface, allowing it to be made fully transparent by setting the factor
|
||||
to 0, fully opaque by setting it to UINT32_MAX, or any value in
|
||||
between.
|
||||
|
||||
Warning! The protocol described in this file is currently in the
|
||||
testing phase. Backward compatible changes may be added together with
|
||||
the corresponding interface version bump. Backward incompatible changes
|
||||
can only be done by creating a new major version of the extension.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the alpha modifier manager object">
|
||||
Destroy the alpha modifier manager. This doesn't destroy objects
|
||||
created with the manager.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="get_surface">
|
||||
<description summary="create a new alpha modifier surface object">
|
||||
Create a new alpha modifier surface object associated with the given
|
||||
wl_surface. If there is already such an object associated with the
|
||||
wl_surface, the already_constructed error will be raised.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="wp_alpha_modifier_surface_v1"/>
|
||||
<arg name="surface" type="object" interface="wl_surface"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="already_constructed" value="0"
|
||||
summary="wl_surface already has a alpha modifier object associated"/>
|
||||
</enum>
|
||||
</interface>
|
||||
|
||||
<interface name="wp_alpha_modifier_surface_v1" version="1">
|
||||
<description summary="modifier object for a surface">
|
||||
This interface allows the client to set a factor for the alpha values on
|
||||
a surface, which can be used to offload such operations to the
|
||||
compositor. Multiple alpha modifiers can be attached to the same
|
||||
surface, in which case the resulting alpha will be the product of all
|
||||
the multiplicative factors.
|
||||
|
||||
The default factor is UINT32_MAX.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="remove the alpha modifier from the surface">
|
||||
This destroys the object, and is equivalent to set_multiplier with
|
||||
a value of UINT32_MAX, with the same double-buffered semantics as
|
||||
set_multiplier.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="set_multiplier">
|
||||
<description summary="set the alpha multiplier">
|
||||
Sets the alpha multiplier for the surface. The alpha multiplier is
|
||||
double-buffered state, see wl_surface.commit for details.
|
||||
|
||||
The default factor is UINT32_MAX.
|
||||
|
||||
This factor is applied in the compositor's blending space, as an
|
||||
additional step after the processing of per-pixel alpha values for
|
||||
the surface. It allows to set an arbitrary alpha value for the
|
||||
surface, including making the surface partially transparent even when
|
||||
all the pixels are fully opaque, or fully transparent even when the
|
||||
pixels are not.
|
||||
</description>
|
||||
<arg name="factor" type="uint" summary="the new alpha multiplier for the surface"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="no_surface" value="0"
|
||||
summary="wl_surface was destroyed"/>
|
||||
</enum>
|
||||
</interface>
|
||||
</protocol>
|
||||
|
|
@ -6,17 +6,18 @@
|
|||
#include <QCursor>
|
||||
#include <QEasingCurve>
|
||||
#include <QGuiApplication>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QScreen>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QStringLiteral>
|
||||
#include <QVariantAnimation>
|
||||
#include <QWidget>
|
||||
#include <QWindow>
|
||||
|
||||
#include <LayerShellQt/window.h>
|
||||
|
||||
#include "../config/Config.h"
|
||||
#include "../wayland/AlphaModifier.h"
|
||||
#include "ghostty.h"
|
||||
|
||||
namespace quickterm {
|
||||
|
|
@ -43,14 +44,36 @@ int animationMs() {
|
|||
return std::clamp(static_cast<int>(secs * 1000.0), 1, 1000);
|
||||
}
|
||||
|
||||
// Apply opacity to the window. Uses wp_alpha_modifier_v1 when the
|
||||
// compositor supports it (real per-surface alpha multiplier on the
|
||||
// compositor side); otherwise falls through to a no-op (the
|
||||
// animation still runs but the window just appears at the end —
|
||||
// previously this called QWindow::setOpacity which spammed
|
||||
// "This plugin does not support setting window opacity" warnings
|
||||
// on every animation tick because QtWayland's QPA plugin has no
|
||||
// implementation).
|
||||
void applyOpacity(QWidget *window, double opacity) {
|
||||
QWindow *handle = window->windowHandle();
|
||||
if (!handle) return;
|
||||
wayland::AlphaModifier::setOpacity(handle, opacity);
|
||||
}
|
||||
|
||||
// Lazily fetch (or build) the per-window opacity animation, parented
|
||||
// to `window` so its lifetime tracks the widget's.
|
||||
QPropertyAnimation *animFor(QWidget *window) {
|
||||
auto *existing = window->property(kAnimProperty).value<QPropertyAnimation *>();
|
||||
// to `window` so its lifetime tracks the widget's. We use
|
||||
// QVariantAnimation (not QPropertyAnimation on windowOpacity) so
|
||||
// the per-tick value is delivered to our applyOpacity handler
|
||||
// instead of QWindow::setOpacity (which QtWayland's QPA plugin
|
||||
// doesn't implement — see applyOpacity comment).
|
||||
QVariantAnimation *animFor(QWidget *window) {
|
||||
auto *existing = window->property(kAnimProperty).value<QVariantAnimation *>();
|
||||
if (existing) return existing;
|
||||
auto *anim = new QPropertyAnimation(window, "windowOpacity", window);
|
||||
auto *anim = new QVariantAnimation(window);
|
||||
QObject::connect(anim, &QVariantAnimation::valueChanged, window,
|
||||
[window](const QVariant &v) {
|
||||
applyOpacity(window, v.toDouble());
|
||||
});
|
||||
window->setProperty(kAnimProperty,
|
||||
QVariant::fromValue<QPropertyAnimation *>(anim));
|
||||
QVariant::fromValue<QVariantAnimation *>(anim));
|
||||
return anim;
|
||||
}
|
||||
|
||||
|
|
@ -167,25 +190,33 @@ void setupLayerShell(QWidget *window) {
|
|||
}
|
||||
|
||||
void animateIn(QWidget *window) {
|
||||
window->setWindowOpacity(0.0);
|
||||
// Show with opacity 0 first so the compositor never paints a
|
||||
// fully-opaque frame before the animation kicks in. The
|
||||
// QVariantAnimation valueChanged → applyOpacity path needs the
|
||||
// wl_surface to exist, which means after show(). We call
|
||||
// applyOpacity twice on either side of show() — once at 0.0 as
|
||||
// a best-effort pre-show (no-op if wl_surface isn't up yet),
|
||||
// once at 0.0 immediately after to lock in the start state.
|
||||
applyOpacity(window, 0.0);
|
||||
window->show();
|
||||
window->raise();
|
||||
window->activateWindow();
|
||||
applyOpacity(window, 0.0);
|
||||
const int ms = animationMs();
|
||||
if (ms <= 0) {
|
||||
window->setWindowOpacity(1.0);
|
||||
applyOpacity(window, 1.0);
|
||||
return;
|
||||
}
|
||||
// Stop any running fade so toggling rapidly doesn't stack
|
||||
// animations.
|
||||
QPropertyAnimation *anim = animFor(window);
|
||||
QVariantAnimation *anim = animFor(window);
|
||||
anim->stop();
|
||||
// animateOut leaves a `finished -> hide()` handler attached to the
|
||||
// shared animation object. If a fade-out was interrupted by this
|
||||
// fade-in (rapid out/in cycle), the leftover handler would fire at
|
||||
// the end of the in-fade and silently hide the just-revealed
|
||||
// window — clear it before starting.
|
||||
QObject::disconnect(anim, &QPropertyAnimation::finished, window, nullptr);
|
||||
QObject::disconnect(anim, &QVariantAnimation::finished, window, nullptr);
|
||||
anim->setDuration(ms);
|
||||
anim->setStartValue(0.0);
|
||||
anim->setEndValue(1.0);
|
||||
|
|
@ -199,17 +230,21 @@ void animateOut(QWidget *window) {
|
|||
window->hide();
|
||||
return;
|
||||
}
|
||||
QPropertyAnimation *anim = animFor(window);
|
||||
QVariantAnimation *anim = animFor(window);
|
||||
anim->stop();
|
||||
anim->setDuration(ms);
|
||||
anim->setStartValue(window->windowOpacity());
|
||||
// Start from the animation's last delivered value if we have one
|
||||
// (a rapid in-then-out cycle interrupts at some intermediate
|
||||
// alpha); otherwise assume the window was fully visible.
|
||||
const QVariant cur = anim->currentValue();
|
||||
anim->setStartValue(cur.isValid() ? cur.toDouble() : 1.0);
|
||||
anim->setEndValue(0.0);
|
||||
anim->setEasingCurve(QEasingCurve::InCubic);
|
||||
// Disconnect any previous handler before reconnecting; otherwise a
|
||||
// toggle-out-then-in cycle accumulates handlers that all fire on
|
||||
// the next out.
|
||||
QObject::disconnect(anim, &QPropertyAnimation::finished, window, nullptr);
|
||||
QObject::connect(anim, &QPropertyAnimation::finished, window,
|
||||
QObject::disconnect(anim, &QVariantAnimation::finished, window, nullptr);
|
||||
QObject::connect(anim, &QVariantAnimation::finished, window,
|
||||
[window]() { window->hide(); });
|
||||
anim->start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,193 @@
|
|||
#include "AlphaModifier.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QWindow>
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "alpha-modifier-v1-client-protocol.h"
|
||||
|
||||
namespace wayland {
|
||||
|
||||
namespace {
|
||||
|
||||
// Process-wide binding. Lazily initialised on first supported()/
|
||||
// setOpacity() call, then read lock-free via the atomic-by-fence
|
||||
// guarantee of `std::call_once`. Once bound it lives for the
|
||||
// process lifetime — there's no clean teardown path on Wayland
|
||||
// global teardown that would matter for a manager-style global.
|
||||
struct GlobalState {
|
||||
wl_display *display = nullptr;
|
||||
wp_alpha_modifier_v1 *manager = nullptr; // null if compositor lacks it
|
||||
bool ready = false; // call_once fired (success or failure)
|
||||
};
|
||||
|
||||
GlobalState &globalState() {
|
||||
static GlobalState g;
|
||||
return g;
|
||||
}
|
||||
|
||||
// Listener: discover wp_alpha_modifier_v1 in the registry. The
|
||||
// scoped wl_event_queue we use here is destroyed before the
|
||||
// listener data goes out of scope, so the registry's child
|
||||
// proxies (none survive past this binding pass) are safe.
|
||||
void onRegistryGlobal(void *data, wl_registry *registry, uint32_t name,
|
||||
const char *interface, uint32_t /*version*/) {
|
||||
auto *g = static_cast<GlobalState *>(data);
|
||||
if (std::strcmp(interface, wp_alpha_modifier_v1_interface.name) != 0)
|
||||
return;
|
||||
// Version 1 is the only version of this staging protocol so far.
|
||||
g->manager = static_cast<wp_alpha_modifier_v1 *>(
|
||||
wl_registry_bind(registry, name, &wp_alpha_modifier_v1_interface, 1));
|
||||
}
|
||||
|
||||
void onRegistryGlobalRemove(void *, wl_registry *, uint32_t) {}
|
||||
|
||||
const wl_registry_listener kRegistryListener = {
|
||||
&onRegistryGlobal,
|
||||
&onRegistryGlobalRemove,
|
||||
};
|
||||
|
||||
// Bind the manager global lazily on first use. Idempotent under
|
||||
// std::call_once. Mirrors the private-queue pattern in
|
||||
// XkbTracker — and like that, we migrate the bound proxy onto
|
||||
// the default queue before destroying the private queue, so
|
||||
// future calls (set_multiplier, get_surface) dispatch on Qt's
|
||||
// event loop instead of a dangling queue.
|
||||
void initOnce() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, []() {
|
||||
auto &g = globalState();
|
||||
QPlatformNativeInterface *native =
|
||||
QGuiApplication::platformNativeInterface();
|
||||
if (!native) {
|
||||
g.ready = true;
|
||||
return;
|
||||
}
|
||||
g.display = static_cast<wl_display *>(
|
||||
native->nativeResourceForIntegration("wl_display"));
|
||||
if (!g.display) {
|
||||
g.ready = true;
|
||||
return;
|
||||
}
|
||||
|
||||
wl_event_queue *queue = wl_display_create_queue(g.display);
|
||||
wl_registry *registry = wl_display_get_registry(g.display);
|
||||
wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(registry), queue);
|
||||
wl_registry_add_listener(registry, &kRegistryListener, &g);
|
||||
wl_display_roundtrip_queue(g.display, queue);
|
||||
wl_registry_destroy(registry);
|
||||
|
||||
// Migrate the manager onto the default queue BEFORE destroying
|
||||
// the private one — otherwise compositor-side messages for the
|
||||
// manager (none expected for this protocol, but cleanliness
|
||||
// matters and Qt's event queue is the dispatch target we want
|
||||
// anyway) would target a destroyed queue, the same footgun that
|
||||
// produced the exit-time SIGSEGV in XkbTracker.
|
||||
if (g.manager) {
|
||||
wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(g.manager), nullptr);
|
||||
}
|
||||
wl_event_queue_destroy(queue);
|
||||
g.ready = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Per-wl_surface alpha modifier object cache. Cached so animation
|
||||
// ticks don't re-roundtrip get_surface every frame.
|
||||
//
|
||||
// Keyed by wl_surface* — that's stable for the wl_surface's
|
||||
// lifetime, and we explicitly drop on detach(). If a QWindow is
|
||||
// destroyed without detach() being called the wl_surface gets
|
||||
// destroyed by Qt; the cached wp_alpha_modifier_surface_v1 would
|
||||
// then be invalid on next get_surface, so callers MUST detach()
|
||||
// from the QWindow's destruction path. Map access is from the
|
||||
// GUI thread only.
|
||||
struct Cache {
|
||||
std::unordered_map<wl_surface *, wp_alpha_modifier_surface_v1 *> entries;
|
||||
};
|
||||
|
||||
Cache &cache() {
|
||||
static Cache c;
|
||||
return c;
|
||||
}
|
||||
|
||||
wl_surface *surfaceFor(QWindow *window) {
|
||||
if (!window) return nullptr;
|
||||
QPlatformNativeInterface *native =
|
||||
QGuiApplication::platformNativeInterface();
|
||||
if (!native) return nullptr;
|
||||
return static_cast<wl_surface *>(
|
||||
native->nativeResourceForWindow("surface", window));
|
||||
}
|
||||
|
||||
wp_alpha_modifier_surface_v1 *getOrCreate(wl_surface *surface) {
|
||||
auto &c = cache();
|
||||
auto it = c.entries.find(surface);
|
||||
if (it != c.entries.end()) return it->second;
|
||||
auto *manager = globalState().manager;
|
||||
if (!manager) return nullptr;
|
||||
auto *obj = wp_alpha_modifier_v1_get_surface(manager, surface);
|
||||
if (!obj) return nullptr;
|
||||
c.entries.emplace(surface, obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool AlphaModifier::supported() {
|
||||
initOnce();
|
||||
return globalState().manager != nullptr;
|
||||
}
|
||||
|
||||
bool AlphaModifier::setOpacity(QWindow *window, double opacity) {
|
||||
initOnce();
|
||||
auto &g = globalState();
|
||||
if (!g.manager) return false;
|
||||
wl_surface *surface = surfaceFor(window);
|
||||
if (!surface) return false;
|
||||
auto *mod = getOrCreate(surface);
|
||||
if (!mod) return false;
|
||||
|
||||
// Convert [0.0, 1.0] → [0, UINT32_MAX]. Clamp first; lround
|
||||
// gives the closest integer, matching what users expect at the
|
||||
// endpoints (1.0 → fully opaque, 0.0 → fully transparent) without
|
||||
// off-by-one rounding drift at intermediate values.
|
||||
const double clamped = std::clamp(opacity, 0.0, 1.0);
|
||||
const uint32_t factor = static_cast<uint32_t>(
|
||||
std::lround(clamped * static_cast<double>(UINT32_MAX)));
|
||||
wp_alpha_modifier_surface_v1_set_multiplier(mod, factor);
|
||||
// Alpha multiplier is double-buffered on the wl_surface; the
|
||||
// change applies on the next wl_surface.commit. Commit here so
|
||||
// the caller doesn't need to know about Wayland's double-buffer
|
||||
// semantics. For Qt-managed top-level windows we don't have a
|
||||
// clean Qt API to force a parent commit, so we wl_surface.commit
|
||||
// the surface directly — same trick used elsewhere in this code
|
||||
// for subsurface state changes.
|
||||
wl_surface_commit(surface);
|
||||
// And flush so the commit reaches the compositor immediately
|
||||
// rather than sitting in libwayland-client's send buffer until
|
||||
// Qt's next event-loop iteration. Otherwise rapid animation
|
||||
// ticks would coalesce into one frame at the end of the tick
|
||||
// cycle, defeating the smooth fade.
|
||||
wl_display_flush(g.display);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AlphaModifier::detach(QWindow *window) {
|
||||
wl_surface *surface = surfaceFor(window);
|
||||
if (!surface) return;
|
||||
auto &c = cache();
|
||||
auto it = c.entries.find(surface);
|
||||
if (it == c.entries.end()) return;
|
||||
wp_alpha_modifier_surface_v1_destroy(it->second);
|
||||
c.entries.erase(it);
|
||||
}
|
||||
|
||||
} // namespace wayland
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Per-window alpha multiplier via wp_alpha_modifier_v1.
|
||||
//
|
||||
// QtWayland's QPA plugin doesn't implement QWindow::setOpacity (it
|
||||
// logs "This plugin does not support setting window opacity" on
|
||||
// every call). For the QuickTerminal fade-in/out we need real
|
||||
// per-surface alpha, so we drive the wp_alpha_modifier_v1 staging
|
||||
// Wayland protocol ourselves.
|
||||
//
|
||||
// Compositor support (as of 2026-05): KWin (KDE 6+), wlroots
|
||||
// (≥0.17), Hyprland — yes. mutter/GNOME — no. If the protocol
|
||||
// isn't advertised, `setOpacity` returns false and the caller can
|
||||
// either skip the animation or fall back to instant show/hide.
|
||||
//
|
||||
// Wayland-only by project decision (see feedback-qt-no-x11 memory).
|
||||
|
||||
#pragma once
|
||||
|
||||
struct wp_alpha_modifier_v1;
|
||||
struct wp_alpha_modifier_surface_v1;
|
||||
class QWindow;
|
||||
|
||||
namespace wayland {
|
||||
|
||||
class AlphaModifier {
|
||||
public:
|
||||
// Returns true if the compositor advertises wp_alpha_modifier_v1
|
||||
// and we've successfully bound it. Cheap after the first call
|
||||
// (the binding is cached process-wide). Use this to decide
|
||||
// whether to drive an opacity animation or fall through to
|
||||
// instant show/hide.
|
||||
static bool supported();
|
||||
|
||||
// Set the window's alpha multiplier in [0.0, 1.0]. Must be
|
||||
// called on the GUI thread (the thread that owns wl_display
|
||||
// dispatch). Returns false if `window`'s native wl_surface
|
||||
// isn't available yet (e.g. before first show), or if the
|
||||
// compositor doesn't support the protocol.
|
||||
//
|
||||
// The wp_alpha_modifier_surface_v1 object is created lazily per
|
||||
// wl_surface and cached for the surface's lifetime — repeated
|
||||
// calls during an animation just emit set_multiplier + commit.
|
||||
static bool setOpacity(QWindow *window, double opacity);
|
||||
|
||||
// Release the per-surface alpha modifier object for this window.
|
||||
// Call when the window is being destroyed (or before re-creating
|
||||
// its native surface). Equivalent to set_multiplier(UINT32_MAX)
|
||||
// followed by destroy on the surface object.
|
||||
static void detach(QWindow *window);
|
||||
};
|
||||
|
||||
} // namespace wayland
|
||||
Loading…
Reference in New Issue