Compare commits

...

14 commits

Author SHA1 Message Date
outfoxxed
8e8f27a22a
wayland/screencopy: enable vulkan dmabuf support on session locks
Some checks failed
Build / Nix-6 (push) Has been cancelled
Build / Nix-7 (push) Has been cancelled
Build / Nix-8 (push) Has been cancelled
Build / Nix-9 (push) Has been cancelled
Build / Nix-10 (push) Has been cancelled
Build / Nix-11 (push) Has been cancelled
Build / Nix-12 (push) Has been cancelled
Build / Nix-13 (push) Has been cancelled
Build / Nix-14 (push) Has been cancelled
Build / Nix-15 (push) Has been cancelled
Build / Nix-16 (push) Has been cancelled
Build / Nix-17 (push) Has been cancelled
Build / Nix-18 (push) Has been cancelled
Build / Nix-19 (push) Has been cancelled
Build / Nix-20 (push) Has been cancelled
Build / Nix-21 (push) Has been cancelled
Build / Nix-22 (push) Has been cancelled
Build / Nix-23 (push) Has been cancelled
Build / Nix-24 (push) Has been cancelled
Build / Nix-25 (push) Has been cancelled
Build / Nix-26 (push) Has been cancelled
Build / Nix-27 (push) Has been cancelled
Build / Nix-28 (push) Has been cancelled
Build / Nix-29 (push) Has been cancelled
Build / Nix-30 (push) Has been cancelled
Build / Nix-31 (push) Has been cancelled
Build / Nix-32 (push) Has been cancelled
Build / Nix-33 (push) Has been cancelled
Build / Archlinux (push) Has been cancelled
Lint / Lint (push) Has been cancelled
Also reformat dmabuf
2026-02-23 23:03:48 -08:00
reakjra
c3c3e2ca25
wayland/screencopy: pin XRGB alpha to 1 in vulkan mode
While EGL handles this internally, vulkan's alpha channel behavior is
undefined when rendering depending on the driver. Notably intel does
not treat it as 1.0.
2026-02-23 22:47:42 -08:00
bbedward
2cf57f43d5
core/proxywindow: expose updatesEnabled property 2026-02-22 22:38:22 -08:00
reakjra
a99519c3ad
wayland/screencopy: support dmabufs in vulkan mode 2026-02-22 22:11:19 -08:00
Bryan Paradis
158db16b93
wayland: check screen isPlaceholder and if wl_output is null
Fixes crashes on disconnected monitors
2026-02-22 19:27:26 -08:00
outfoxxed
e7cd1e9982
core: add env and isEnvSet functions to pragma context
Some checks failed
Build / Nix-6 (push) Has been cancelled
Build / Nix-7 (push) Has been cancelled
Build / Nix-8 (push) Has been cancelled
Build / Nix-9 (push) Has been cancelled
Build / Nix-10 (push) Has been cancelled
Build / Nix-11 (push) Has been cancelled
Build / Nix-12 (push) Has been cancelled
Build / Nix-13 (push) Has been cancelled
Build / Nix-14 (push) Has been cancelled
Build / Nix-15 (push) Has been cancelled
Build / Nix-16 (push) Has been cancelled
Build / Nix-17 (push) Has been cancelled
Build / Nix-18 (push) Has been cancelled
Build / Nix-19 (push) Has been cancelled
Build / Nix-20 (push) Has been cancelled
Build / Nix-21 (push) Has been cancelled
Build / Nix-22 (push) Has been cancelled
Build / Nix-23 (push) Has been cancelled
Build / Nix-24 (push) Has been cancelled
Build / Nix-25 (push) Has been cancelled
Build / Nix-26 (push) Has been cancelled
Build / Nix-27 (push) Has been cancelled
Build / Nix-28 (push) Has been cancelled
Build / Nix-29 (push) Has been cancelled
Build / Nix-30 (push) Has been cancelled
Build / Nix-31 (push) Has been cancelled
Build / Nix-32 (push) Has been cancelled
Build / Nix-33 (push) Has been cancelled
Build / Archlinux (push) Has been cancelled
Lint / Lint (push) Has been cancelled
2026-02-21 21:11:45 -08:00
Andrei
afbc5ffd4e
services/pipewire: use node volume control when device missing
Some outputs which present a pipewire device object do not present
routes, instead expecting volume to be set on the associated pipewire node.
2026-02-21 20:39:03 -08:00
outfoxxed
dacfa9de82
widgets/cliprect: use ShaderEffectSource to propagate mouse events
Some checks failed
Build / Nix-6 (push) Has been cancelled
Build / Nix-7 (push) Has been cancelled
Build / Nix-8 (push) Has been cancelled
Build / Nix-9 (push) Has been cancelled
Build / Nix-10 (push) Has been cancelled
Build / Nix-11 (push) Has been cancelled
Build / Nix-12 (push) Has been cancelled
Build / Nix-13 (push) Has been cancelled
Build / Nix-14 (push) Has been cancelled
Build / Nix-15 (push) Has been cancelled
Build / Nix-16 (push) Has been cancelled
Build / Nix-17 (push) Has been cancelled
Build / Nix-18 (push) Has been cancelled
Build / Nix-19 (push) Has been cancelled
Build / Nix-20 (push) Has been cancelled
Build / Nix-21 (push) Has been cancelled
Build / Nix-22 (push) Has been cancelled
Build / Nix-23 (push) Has been cancelled
Build / Nix-24 (push) Has been cancelled
Build / Nix-25 (push) Has been cancelled
Build / Nix-26 (push) Has been cancelled
Build / Nix-27 (push) Has been cancelled
Build / Nix-28 (push) Has been cancelled
Build / Nix-29 (push) Has been cancelled
Build / Nix-30 (push) Has been cancelled
Build / Nix-31 (push) Has been cancelled
Build / Nix-32 (push) Has been cancelled
Build / Nix-33 (push) Has been cancelled
Build / Archlinux (push) Has been cancelled
Lint / Lint (push) Has been cancelled
2026-02-09 19:14:36 -08:00
outfoxxed
4429c03837
widgets/cliprect: fix ShaderEffect warnings on reload
layer.effect causes warnings on reload for an unknown reason which
seems to be ownership or destruction time related. This commit uses
an alternate strategy to create the shader which does not show this
warning.
2026-02-08 20:10:11 -08:00
kossLAN
395a1301a8
core: add hasThemeIcon mapping
Some checks failed
Build / Nix-6 (push) Has been cancelled
Build / Nix-7 (push) Has been cancelled
Build / Nix-8 (push) Has been cancelled
Build / Nix-9 (push) Has been cancelled
Build / Nix-10 (push) Has been cancelled
Build / Nix-11 (push) Has been cancelled
Build / Nix-12 (push) Has been cancelled
Build / Nix-13 (push) Has been cancelled
Build / Nix-14 (push) Has been cancelled
Build / Nix-15 (push) Has been cancelled
Build / Nix-16 (push) Has been cancelled
Build / Nix-17 (push) Has been cancelled
Build / Nix-18 (push) Has been cancelled
Build / Nix-19 (push) Has been cancelled
Build / Nix-20 (push) Has been cancelled
Build / Nix-21 (push) Has been cancelled
Build / Nix-22 (push) Has been cancelled
Build / Nix-23 (push) Has been cancelled
Build / Nix-24 (push) Has been cancelled
Build / Nix-25 (push) Has been cancelled
Build / Nix-26 (push) Has been cancelled
Build / Nix-27 (push) Has been cancelled
Build / Nix-28 (push) Has been cancelled
Build / Nix-29 (push) Has been cancelled
Build / Nix-30 (push) Has been cancelled
Build / Nix-31 (push) Has been cancelled
Build / Nix-32 (push) Has been cancelled
Build / Nix-33 (push) Has been cancelled
Build / Archlinux (push) Has been cancelled
Lint / Lint (push) Has been cancelled
2026-02-08 01:26:08 -08:00
outfoxxed
1e4d804e7f
widgets/cliprect: use layer.effect on content item over property
Some checks failed
Build / Nix-6 (push) Has been cancelled
Build / Nix-7 (push) Has been cancelled
Build / Nix-8 (push) Has been cancelled
Build / Nix-9 (push) Has been cancelled
Build / Nix-10 (push) Has been cancelled
Build / Nix-11 (push) Has been cancelled
Build / Nix-12 (push) Has been cancelled
Build / Nix-13 (push) Has been cancelled
Build / Nix-14 (push) Has been cancelled
Build / Nix-15 (push) Has been cancelled
Build / Nix-16 (push) Has been cancelled
Build / Nix-17 (push) Has been cancelled
Build / Nix-18 (push) Has been cancelled
Build / Nix-19 (push) Has been cancelled
Build / Nix-20 (push) Has been cancelled
Build / Nix-21 (push) Has been cancelled
Build / Nix-22 (push) Has been cancelled
Build / Nix-23 (push) Has been cancelled
Build / Nix-24 (push) Has been cancelled
Build / Nix-25 (push) Has been cancelled
Build / Nix-26 (push) Has been cancelled
Build / Nix-27 (push) Has been cancelled
Build / Nix-28 (push) Has been cancelled
Build / Nix-29 (push) Has been cancelled
Build / Nix-30 (push) Has been cancelled
Build / Nix-31 (push) Has been cancelled
Build / Nix-32 (push) Has been cancelled
Build / Nix-33 (push) Has been cancelled
Build / Archlinux (push) Has been cancelled
Lint / Lint (push) Has been cancelled
ShaderEffectSource as a property not parented to an item does not
update its sourceItem's QQuickWindow when its own is changed. This
lead to use after frees and broken effects when using ClippingRectangle.
2026-01-28 01:43:31 -08:00
Manuel Romei
191085a882
ipc: use deleteLater() in IpcServerConnection to prevent use-after-free
Some checks failed
Build / Nix-6 (push) Has been cancelled
Build / Nix-7 (push) Has been cancelled
Build / Nix-8 (push) Has been cancelled
Build / Nix-9 (push) Has been cancelled
Build / Nix-10 (push) Has been cancelled
Build / Nix-11 (push) Has been cancelled
Build / Nix-12 (push) Has been cancelled
Build / Nix-13 (push) Has been cancelled
Build / Nix-14 (push) Has been cancelled
Build / Nix-15 (push) Has been cancelled
Build / Nix-16 (push) Has been cancelled
Build / Nix-17 (push) Has been cancelled
Build / Nix-18 (push) Has been cancelled
Build / Nix-19 (push) Has been cancelled
Build / Nix-20 (push) Has been cancelled
Build / Nix-21 (push) Has been cancelled
Build / Nix-22 (push) Has been cancelled
Build / Nix-23 (push) Has been cancelled
Build / Nix-24 (push) Has been cancelled
Build / Nix-25 (push) Has been cancelled
Build / Nix-26 (push) Has been cancelled
Build / Nix-27 (push) Has been cancelled
Build / Nix-28 (push) Has been cancelled
Build / Nix-29 (push) Has been cancelled
Build / Nix-30 (push) Has been cancelled
Build / Nix-31 (push) Has been cancelled
Build / Nix-32 (push) Has been cancelled
Build / Nix-33 (push) Has been cancelled
Build / Archlinux (push) Has been cancelled
Lint / Lint (push) Has been cancelled
2026-01-21 00:50:39 -08:00
bbedward
8fd0de4580 core/proxywindow: create window on visibility for lazily initialized windows 2026-01-20 16:10:45 -05:00
bbedward
7a427ce197 core: fix inverted inHeader condition in preprocesso
Some checks failed
Build / Nix-6 (push) Has been cancelled
Build / Nix-7 (push) Has been cancelled
Build / Nix-8 (push) Has been cancelled
Build / Nix-9 (push) Has been cancelled
Build / Nix-10 (push) Has been cancelled
Build / Nix-11 (push) Has been cancelled
Build / Nix-12 (push) Has been cancelled
Build / Nix-13 (push) Has been cancelled
Build / Nix-14 (push) Has been cancelled
Build / Nix-15 (push) Has been cancelled
Build / Nix-16 (push) Has been cancelled
Build / Nix-17 (push) Has been cancelled
Build / Nix-18 (push) Has been cancelled
Build / Nix-19 (push) Has been cancelled
Build / Nix-20 (push) Has been cancelled
Build / Nix-21 (push) Has been cancelled
Build / Nix-22 (push) Has been cancelled
Build / Nix-23 (push) Has been cancelled
Build / Nix-24 (push) Has been cancelled
Build / Nix-25 (push) Has been cancelled
Build / Nix-26 (push) Has been cancelled
Build / Nix-27 (push) Has been cancelled
Build / Nix-28 (push) Has been cancelled
Build / Nix-29 (push) Has been cancelled
Build / Nix-30 (push) Has been cancelled
Build / Nix-31 (push) Has been cancelled
Build / Nix-32 (push) Has been cancelled
Build / Nix-33 (push) Has been cancelled
Build / Archlinux (push) Has been cancelled
Lint / Lint (push) Has been cancelled
2026-01-17 17:30:40 -05:00
25 changed files with 546 additions and 43 deletions

View file

@ -50,6 +50,7 @@ jobs:
wayland-protocols \
wayland \
libdrm \
vulkan-headers \
libxcb \
libpipewire \
cli11 \

View file

@ -146,6 +146,7 @@ To disable: `-DSCREENCOPY=OFF`
Dependencies:
- `libdrm`
- `libgbm`
- `vulkan-headers` (build-time)
Specific protocols can also be disabled:
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]

View file

@ -24,6 +24,8 @@ set shell id.
- Added support for grabbing focus from popup windows.
- Added support for IPC signal listeners.
- Added Quickshell version checking and version gated preprocessing.
- Added a way to detect if an icon is from the system icon theme or not.
- Added vulkan support to screencopy.
## Other Changes
@ -35,6 +37,7 @@ set shell id.
- Fixed volume control breaking with pipewire pro audio mode.
- Fixed volume control breaking with bluez streams and potentially others.
- Fixed volume control breaking for devices without route definitions.
- Fixed escape sequence handling in desktop entries.
- Fixed volumes not initializing if a pipewire device was already loaded before its node.
- Fixed hyprland active toplevel not resetting after window closes.
@ -43,7 +46,10 @@ set shell id.
- Fixed asynchronous loaders not working after reload.
- Fixed asynchronous loaders not working before window creation.
- Fixed memory leak in IPC handlers.
- Fixed ClippingRectangle related crashes.
- Fixed crashes when monitors are unplugged.
## Packaging Changes
`glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
`vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).

View file

@ -19,6 +19,7 @@
xorg,
libdrm,
libgbm ? null,
vulkan-headers,
pipewire,
pam,
polkit,
@ -77,7 +78,7 @@
++ lib.optional withJemalloc jemalloc
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
++ lib.optionals withWayland [ wayland wayland-protocols ]
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
++ lib.optional withX11 xorg.libxcb
++ lib.optional withPam pam
++ lib.optional withPipewire pipewire

View file

@ -314,6 +314,8 @@ QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback)
return IconImageProvider::requestString(icon, "", fallback);
}
bool QuickshellGlobal::hasThemeIcon(const QString& icon) { return QIcon::hasThemeIcon(icon); }
bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor, const QStringList& features) {
return qs::scan::env::PreprocEnv::hasVersion(major, minor, features);
}

View file

@ -202,6 +202,8 @@ public:
/// Setting the `fallback` parameter of `iconPath` will attempt to load the fallback
/// icon if the requested one could not be loaded.
Q_INVOKABLE static QString iconPath(const QString& icon, const QString& fallback);
/// Check if specified icon has an available icon in your icon theme
Q_INVOKABLE static bool hasThemeIcon(const QString& icon);
/// Equivalent to `${Quickshell.configDir}/${path}`
Q_INVOKABLE [[nodiscard]] QString shellPath(const QString& path) const;
/// > [!WARNING] Deprecated: Renamed to @@shellPath() for clarity.

View file

@ -118,7 +118,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
auto stream = QTextStream(&file);
auto imports = QVector<QString>();
bool inHeader = false;
bool inHeader = true;
auto ifScopes = QVector<bool>();
bool sourceMasked = false;
int lineNum = 0;
@ -177,7 +177,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
} else if (!internal && line == "//@ pragma Internal") {
internal = true;
} else if (line.contains('{')) {
inHeader = true;
inHeader = false;
}
}

View file

@ -1,6 +1,7 @@
#include "scanenv.hpp"
#include <qcontainerfwd.h>
#include <qtenvironmentvariables.h>
#include "build.hpp"
@ -19,4 +20,12 @@ bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
}
QString PreprocEnv::env(const QString& variable) {
return qEnvironmentVariable(variable.toStdString().c_str());
}
bool PreprocEnv::isEnvSet(const QString& variable) {
return qEnvironmentVariableIsSet(variable.toStdString().c_str());
}
} // namespace qs::scan::env

View file

@ -12,6 +12,9 @@ class PreprocEnv: public QObject {
public:
Q_INVOKABLE static bool
hasVersion(int major, int minor, const QStringList& features = QStringList());
Q_INVOKABLE static QString env(const QString& variable);
Q_INVOKABLE static bool isEnvSet(const QString& variable);
};
} // namespace qs::scan::env

View file

@ -61,7 +61,7 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server
void IpcServerConnection::onDisconnected() {
qCInfo(logIpc) << "IPC connection disconnected" << this;
delete this;
this->deleteLater();
}
void IpcServerConnection::onReadyRead() {
@ -88,7 +88,7 @@ void IpcServerConnection::onReadyRead() {
// async connections reparent
if (dynamic_cast<IpcServer*>(this->parent()) != nullptr) {
delete this;
this->deleteLater();
}
}

View file

@ -141,6 +141,10 @@ bool PwDevice::tryLoadVolumeProps(qint32 routeDevice, PwVolumeProps& volumeProps
return true;
}
bool PwDevice::hasRouteDevice(qint32 routeDevice) const {
return this->routeDeviceIndexes.contains(routeDevice);
}
void PwDevice::polled() {
// It is far more likely that the list content has not come in yet than it having no entries,
// and there isn't a way to check in the case that there *aren't* actually any entries.

View file

@ -12,13 +12,15 @@
#include <spa/pod/builder.h>
#include "core.hpp"
#include "node.hpp"
#include "registry.hpp"
namespace qs::service::pipewire {
class PwDevice;
// Forward declare to avoid circular dependency with node.hpp
struct PwVolumeProps;
class PwDevice: public PwBindable<pw_device, PW_TYPE_INTERFACE_Device, PW_VERSION_DEVICE> {
Q_OBJECT;
@ -33,6 +35,7 @@ public:
[[nodiscard]] bool waitingForDevice() const;
[[nodiscard]] bool tryLoadVolumeProps(qint32 routeDevice, PwVolumeProps& volumeProps);
[[nodiscard]] bool hasRouteDevice(qint32 routeDevice) const;
signals:
void deviceReady();

View file

@ -429,6 +429,10 @@ void PwNodeBoundAudio::setMuted(bool muted) {
}
float PwNodeBoundAudio::averageVolume() const {
if (this->mVolumes.isEmpty()) {
return 0.0f;
}
float total = 0;
for (auto volume: this->mVolumes) {
@ -572,22 +576,28 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
const auto* muteProp = spa_pod_find_prop(param, nullptr, SPA_PROP_mute);
const auto* volumeStepProp = spa_pod_find_prop(param, nullptr, SPA_PROP_volumeStep);
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value);
const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value);
spa_pod* iter = nullptr;
SPA_POD_ARRAY_FOREACH(volumes, iter) {
// Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly.
auto linear = *reinterpret_cast<float*>(iter);
auto visual = std::cbrt(linear);
props.volumes.push_back(visual);
if (volumesProp) {
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value);
spa_pod* iter = nullptr;
SPA_POD_ARRAY_FOREACH(volumes, iter) {
// Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly.
auto linear = *reinterpret_cast<float*>(iter);
auto visual = std::cbrt(linear);
props.volumes.push_back(visual);
}
}
SPA_POD_ARRAY_FOREACH(channels, iter) {
props.channels.push_back(*reinterpret_cast<PwAudioChannel::Enum*>(iter));
if (channelsProp) {
const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value);
spa_pod* iter = nullptr;
SPA_POD_ARRAY_FOREACH(channels, iter) {
props.channels.push_back(*reinterpret_cast<PwAudioChannel::Enum*>(iter));
}
}
spa_pod_get_bool(&muteProp->value, &props.mute);
if (muteProp) {
spa_pod_get_bool(&muteProp->value, &props.mute);
}
if (volumeStepProp) {
spa_pod_get_float(&volumeStepProp->value, &props.volumeStep);

View file

@ -15,6 +15,7 @@
#include <spa/pod/pod.h>
#include "core.hpp"
#include "device.hpp"
#include "registry.hpp"
namespace qs::service::pipewire {
@ -249,7 +250,9 @@ public:
bool proAudio = false;
[[nodiscard]] bool shouldUseDevice() const {
return this->device && !this->proAudio && this->routeDevice != -1;
if (!this->device || this->proAudio || this->routeDevice == -1) return false;
// Only use device control if the device actually has route indexes for this routeDevice
return this->device->hasRouteDevice(this->routeDevice);
}
signals:

View file

@ -1,6 +1,8 @@
find_package(PkgConfig REQUIRED)
pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl)
find_package(VulkanHeaders REQUIRED)
qt_add_library(quickshell-wayland-buffer STATIC
manager.cpp
dmabuf.cpp
@ -10,9 +12,10 @@ qt_add_library(quickshell-wayland-buffer STATIC
wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf")
target_link_libraries(quickshell-wayland-buffer PRIVATE
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
Qt::Quick Qt::QuickPrivate Qt::GuiPrivate Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
PkgConfig::dmabuf-deps
wlp-linux-dmabuf
Vulkan::Headers
)
qs_pch(quickshell-wayland-buffer SET large)

View file

@ -14,6 +14,8 @@
#include <fcntl.h>
#include <gbm.h>
#include <libdrm/drm_fourcc.h>
#include <private/qquickwindow_p.h>
#include <private/qrhivulkan_p.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qlist.h>
@ -24,12 +26,17 @@
#include <qpair.h>
#include <qquickwindow.h>
#include <qscopedpointer.h>
#include <qsgrendererinterface.h>
#include <qsgtexture_platform.h>
#include <qtypes.h>
#include <qvulkanfunctions.h>
#include <qvulkaninstance.h>
#include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <vulkan/vulkan_core.h>
#include <wayland-client-protocol.h>
#include <wayland-linux-dmabuf-v1-client-protocol.h>
#include <wayland-util.h>
@ -48,6 +55,36 @@ QS_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg)
LinuxDmabufManager* MANAGER = nullptr; // NOLINT
VkFormat drmFormatToVkFormat(uint32_t drmFormat) {
// NOLINTBEGIN(bugprone-branch-clone): XRGB/ARGB intentionally map to the same VK format
switch (drmFormat) {
case DRM_FORMAT_ARGB8888: return VK_FORMAT_B8G8R8A8_UNORM;
case DRM_FORMAT_XRGB8888: return VK_FORMAT_B8G8R8A8_UNORM;
case DRM_FORMAT_ABGR8888: return VK_FORMAT_R8G8B8A8_UNORM;
case DRM_FORMAT_XBGR8888: return VK_FORMAT_R8G8B8A8_UNORM;
case DRM_FORMAT_ARGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case DRM_FORMAT_XRGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case DRM_FORMAT_ABGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
case DRM_FORMAT_XBGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
case DRM_FORMAT_ABGR16161616F: return VK_FORMAT_R16G16B16A16_SFLOAT;
case DRM_FORMAT_RGB565: return VK_FORMAT_R5G6B5_UNORM_PACK16;
case DRM_FORMAT_BGR565: return VK_FORMAT_B5G6R5_UNORM_PACK16;
default: return VK_FORMAT_UNDEFINED;
}
// NOLINTEND(bugprone-branch-clone)
}
bool drmFormatHasAlpha(uint32_t drmFormat) {
switch (drmFormat) {
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_ARGB2101010:
case DRM_FORMAT_ABGR2101010:
case DRM_FORMAT_ABGR16161616F: return true;
default: return false;
}
}
} // namespace
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) {
@ -81,25 +118,27 @@ GbmDeviceHandle::~GbmDeviceHandle() {
}
}
// This will definitely backfire later
// Prefer ARGB over XRGB: XRGB has undefined alpha bytes which cause
// transparency artifacts on Vulkan (notably Intel GPUs) since Vulkan
// doesn't auto-fill alpha=1.0 for X formats like EGL does.
void LinuxDmabufFormatSelection::ensureSorted() {
if (this->sorted) return;
auto beginIter = this->formats.begin();
auto xrgbIter = std::ranges::find_if(this->formats, [](const auto& format) {
return format.first == DRM_FORMAT_XRGB8888;
});
if (xrgbIter != this->formats.end()) {
std::swap(*beginIter, *xrgbIter);
++beginIter;
}
auto argbIter = std::ranges::find_if(this->formats, [](const auto& format) {
return format.first == DRM_FORMAT_ARGB8888;
});
if (argbIter != this->formats.end()) std::swap(*beginIter, *argbIter);
if (argbIter != this->formats.end()) {
std::swap(*beginIter, *argbIter);
++beginIter;
}
auto xrgbIter = std::ranges::find_if(this->formats, [](const auto& format) {
return format.first == DRM_FORMAT_XRGB8888;
});
if (xrgbIter != this->formats.end()) std::swap(*beginIter, *xrgbIter);
this->sorted = true;
}
@ -532,6 +571,15 @@ bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const {
}
WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
auto* ri = window->rendererInterface();
if (ri && ri->graphicsApi() == QSGRendererInterface::Vulkan) {
return this->createQsgTextureVulkan(window);
}
return this->createQsgTextureGl(window);
}
WlBufferQSGTexture* WlDmaBuffer::createQsgTextureGl(QQuickWindow* window) const {
static auto* glEGLImageTargetTexture2DOES = []() {
auto* fn = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
eglGetProcAddress("glEGLImageTargetTexture2DOES")
@ -662,6 +710,313 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
return tex;
}
WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) const {
auto* ri = window->rendererInterface();
auto* vkInst = window->vulkanInstance();
if (!vkInst) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: no QVulkanInstance.";
return nullptr;
}
auto* vkDevicePtr =
static_cast<VkDevice*>(ri->getResource(window, QSGRendererInterface::DeviceResource));
auto* vkPhysDevicePtr = static_cast<VkPhysicalDevice*>(
ri->getResource(window, QSGRendererInterface::PhysicalDeviceResource)
);
if (!vkDevicePtr || !vkPhysDevicePtr) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: could not get Vulkan device.";
return nullptr;
}
VkDevice device = *vkDevicePtr;
VkPhysicalDevice physDevice = *vkPhysDevicePtr;
auto* devFuncs = vkInst->deviceFunctions(device);
auto* instFuncs = vkInst->functions();
if (!devFuncs || !instFuncs) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: "
"could not get Vulkan functions.";
return nullptr;
}
auto getMemoryFdPropertiesKHR = reinterpret_cast<PFN_vkGetMemoryFdPropertiesKHR>(
instFuncs->vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR")
);
if (!getMemoryFdPropertiesKHR) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: "
"vkGetMemoryFdPropertiesKHR not available. "
"Missing VK_KHR_external_memory_fd extension.";
return nullptr;
}
const VkFormat vkFormat = drmFormatToVkFormat(this->format);
if (vkFormat == VK_FORMAT_UNDEFINED) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: unsupported DRM format"
<< FourCCStr(this->format);
return nullptr;
}
if (this->planeCount > 4) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: too many planes"
<< this->planeCount;
return nullptr;
}
std::array<VkSubresourceLayout, 4> planeLayouts = {};
for (int i = 0; i < this->planeCount; ++i) {
planeLayouts[i].offset = this->planes[i].offset; // NOLINT
planeLayouts[i].rowPitch = this->planes[i].stride; // NOLINT
planeLayouts[i].size = 0;
planeLayouts[i].arrayPitch = 0;
planeLayouts[i].depthPitch = 0;
}
const bool useModifier = this->modifier != DRM_FORMAT_MOD_INVALID;
VkExternalMemoryImageCreateInfo externalInfo = {};
externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
VkImageDrmFormatModifierExplicitCreateInfoEXT modifierInfo = {};
modifierInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT;
modifierInfo.drmFormatModifier = this->modifier;
modifierInfo.drmFormatModifierPlaneCount = static_cast<uint32_t>(this->planeCount);
modifierInfo.pPlaneLayouts = planeLayouts.data();
if (useModifier) {
externalInfo.pNext = &modifierInfo;
}
VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.pNext = &externalInfo;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = vkFormat;
imageInfo.extent = {.width = this->width, .height = this->height, .depth = 1};
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = useModifier ? VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT : VK_IMAGE_TILING_LINEAR;
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImage image = VK_NULL_HANDLE;
VkResult result = devFuncs->vkCreateImage(device, &imageInfo, nullptr, &image);
if (result != VK_SUCCESS) {
qCWarning(logDmabuf) << "Failed to create VkImage for DMA-BUF import, result:" << result;
return nullptr;
}
VkDeviceMemory memory = VK_NULL_HANDLE;
// dup() is required because vkAllocateMemory with VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT
// takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close.
const int dupFd =
dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
if (dupFd < 0) {
qCWarning(logDmabuf) << "Failed to dup() fd for DMA-BUF import";
goto cleanup_fail; // NOLINT
}
{
VkMemoryRequirements memReqs = {};
devFuncs->vkGetImageMemoryRequirements(device, image, &memReqs);
VkMemoryFdPropertiesKHR fdProps = {};
fdProps.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR;
result = getMemoryFdPropertiesKHR(
device,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
dupFd,
&fdProps
);
if (result != VK_SUCCESS) {
close(dupFd);
qCWarning(logDmabuf) << "vkGetMemoryFdPropertiesKHR failed, result:" << result;
goto cleanup_fail; // NOLINT
}
const uint32_t memTypeBits = memReqs.memoryTypeBits & fdProps.memoryTypeBits;
VkPhysicalDeviceMemoryProperties memProps = {};
instFuncs->vkGetPhysicalDeviceMemoryProperties(physDevice, &memProps);
uint32_t memTypeIndex = UINT32_MAX;
for (uint32_t j = 0; j < memProps.memoryTypeCount; ++j) {
if (memTypeBits & (1u << j)) {
memTypeIndex = j;
break;
}
}
if (memTypeIndex == UINT32_MAX) {
close(dupFd);
qCWarning(logDmabuf) << "No compatible memory type for DMA-BUF import";
goto cleanup_fail; // NOLINT
}
VkImportMemoryFdInfoKHR importInfo = {};
importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR;
importInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
importInfo.fd = dupFd;
VkMemoryDedicatedAllocateInfo dedicatedInfo = {};
dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
dedicatedInfo.image = image;
dedicatedInfo.pNext = &importInfo;
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.pNext = &dedicatedInfo;
allocInfo.allocationSize = memReqs.size;
allocInfo.memoryTypeIndex = memTypeIndex;
result = devFuncs->vkAllocateMemory(device, &allocInfo, nullptr, &memory);
if (result != VK_SUCCESS) {
close(dupFd);
qCWarning(logDmabuf) << "vkAllocateMemory failed, result:" << result;
goto cleanup_fail; // NOLINT
}
result = devFuncs->vkBindImageMemory(device, image, memory, 0);
if (result != VK_SUCCESS) {
qCWarning(logDmabuf) << "vkBindImageMemory failed, result:" << result;
goto cleanup_fail; // NOLINT
}
}
{
// acquire the DMA-BUF from the foreign (compositor) queue and transition
// to shader-read layout. oldLayout must be GENERAL (not UNDEFINED) to
// preserve the DMA-BUF contents written by the external producer. Hopefully.
window->beginExternalCommands();
auto* cmdBufPtr = static_cast<VkCommandBuffer*>(
ri->getResource(window, QSGRendererInterface::CommandListResource)
);
if (cmdBufPtr && *cmdBufPtr) {
VkCommandBuffer cmdBuf = *cmdBufPtr;
// find the graphics queue family index for the ownrship transfer.
uint32_t graphicsQueueFamily = 0;
uint32_t queueFamilyCount = 0;
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(physDevice, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(
physDevice,
&queueFamilyCount,
queueFamilies.data()
);
for (uint32_t i = 0; i < queueFamilyCount; ++i) {
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
graphicsQueueFamily = i;
break;
}
}
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT;
barrier.dstQueueFamilyIndex = graphicsQueueFamily;
barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
devFuncs->vkCmdPipelineBarrier(
cmdBuf,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0,
nullptr,
0,
nullptr,
1,
&barrier
);
}
window->endExternalCommands();
auto* qsgTexture = QQuickWindowPrivate::get(window)->createTextureFromNativeTexture(
reinterpret_cast<quint64>(image),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
static_cast<uint>(vkFormat),
QSize(static_cast<int>(this->width), static_cast<int>(this->height)),
{}
);
// For opaque DRM formats (XRGB, XBGR, etc.), the alpha bytes are underfined.
// EGL silently forces alpha=1.0 for these, but Vulkan doesn't. Replace Qt's
// default identity-swizzle VkImageView with one that maps alpha to ONE.
if (!drmFormatHasAlpha(this->format)) {
auto* vkTexture = static_cast<QVkTexture*>(qsgTexture->rhiTexture()); // NOLINT
devFuncs->vkDestroyImageView(device, vkTexture->imageView, nullptr);
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = vkFormat;
viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.components.a = VK_COMPONENT_SWIZZLE_ONE;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.layerCount = 1;
result = devFuncs->vkCreateImageView(device, &viewInfo, nullptr, &vkTexture->imageView);
if (result != VK_SUCCESS) {
qCWarning(logDmabuf) << "Failed to create alpha-swizzled VkImageView, result:" << result;
}
}
auto* tex = new WlDmaBufferVulkanQSGTexture(devFuncs, device, image, memory, qsgTexture);
qCDebug(logDmabuf) << "Created WlDmaBufferVulkanQSGTexture" << tex << "from" << this;
return tex;
}
cleanup_fail:
if (image != VK_NULL_HANDLE) {
devFuncs->vkDestroyImage(device, image, nullptr);
}
if (memory != VK_NULL_HANDLE) {
devFuncs->vkFreeMemory(device, memory, nullptr);
}
return nullptr;
}
WlDmaBufferVulkanQSGTexture::~WlDmaBufferVulkanQSGTexture() {
delete this->qsgTexture;
if (this->image != VK_NULL_HANDLE) {
this->devFuncs->vkDestroyImage(this->device, this->image, nullptr);
}
if (this->memory != VK_NULL_HANDLE) {
this->devFuncs->vkFreeMemory(this->device, this->memory, nullptr);
}
qCDebug(logDmabuf) << "WlDmaBufferVulkanQSGTexture" << this << "destroyed.";
}
WlDmaBufferQSGTexture::~WlDmaBufferQSGTexture() {
auto* context = QOpenGLContext::currentContext();
auto* display = context->nativeInterface<QNativeInterface::QEGLContext>()->display();

View file

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <cstdint>
#include <EGL/egl.h>
@ -12,9 +13,11 @@
#include <qsize.h>
#include <qtclasshelpermacros.h>
#include <qtypes.h>
#include <qvulkanfunctions.h>
#include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h>
#include <sys/types.h>
#include <vulkan/vulkan_core.h>
#include <wayland-linux-dmabuf-v1-client-protocol.h>
#include <wayland-util.h>
#include <xf86drm.h>
@ -114,6 +117,36 @@ private:
friend class WlDmaBuffer;
};
class WlDmaBufferVulkanQSGTexture: public WlBufferQSGTexture {
public:
~WlDmaBufferVulkanQSGTexture() override;
Q_DISABLE_COPY_MOVE(WlDmaBufferVulkanQSGTexture);
[[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture; }
private:
WlDmaBufferVulkanQSGTexture(
QVulkanDeviceFunctions* devFuncs,
VkDevice device,
VkImage image,
VkDeviceMemory memory,
QSGTexture* qsgTexture
)
: devFuncs(devFuncs)
, device(device)
, image(image)
, memory(memory)
, qsgTexture(qsgTexture) {}
QVulkanDeviceFunctions* devFuncs = nullptr;
VkDevice device = VK_NULL_HANDLE;
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
QSGTexture* qsgTexture = nullptr;
friend class WlDmaBuffer;
};
class WlDmaBuffer: public WlBuffer {
public:
~WlDmaBuffer() override;
@ -151,6 +184,9 @@ private:
friend class LinuxDmabufManager;
friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
[[nodiscard]] WlBufferQSGTexture* createQsgTextureGl(QQuickWindow* window) const;
[[nodiscard]] WlBufferQSGTexture* createQsgTextureVulkan(QQuickWindow* window) const;
};
QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);

View file

@ -9,6 +9,7 @@
#include <qqmlcomponent.h>
#include <qqmlengine.h>
#include <qqmllist.h>
#include <qquickgraphicsconfiguration.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qscreen.h>
@ -216,6 +217,15 @@ void WlSessionLockSurface::onReload(QObject* oldInstance) {
if (this->window == nullptr) {
this->window = new QQuickWindow();
// needed for vulkan dmabuf import, qt ignores these if not applicable
auto graphicsConfig = this->window->graphicsConfiguration();
graphicsConfig.setDeviceExtensions({
"VK_KHR_external_memory_fd",
"VK_EXT_external_memory_dma_buf",
"VK_EXT_image_drm_format_modifier",
});
this->window->setGraphicsConfiguration(graphicsConfig);
}
this->mContentItem->setParentItem(this->window->contentItem());

View file

@ -28,7 +28,7 @@ QSWaylandSessionLockSurface::QSWaylandSessionLockSurface(QtWaylandClient::QWayla
wl_output* output = nullptr; // NOLINT (include)
auto* waylandScreen = dynamic_cast<QtWaylandClient::QWaylandScreen*>(qwindow->screen()->handle());
if (waylandScreen != nullptr) {
if (waylandScreen != nullptr && !waylandScreen->isPlaceholder() && waylandScreen->output()) {
output = waylandScreen->output();
} else {
qFatal() << "Session lock screen does not corrospond to a real screen. Force closing window";

View file

@ -143,11 +143,11 @@ LayerSurface::LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWayla
auto* waylandScreen =
dynamic_cast<QtWaylandClient::QWaylandScreen*>(qwindow->screen()->handle());
if (waylandScreen != nullptr) {
if (waylandScreen != nullptr && !waylandScreen->isPlaceholder() && waylandScreen->output()) {
output = waylandScreen->output();
} else {
qWarning()
<< "Layershell screen does not corrospond to a real screen. Letting the compositor pick.";
<< "Layershell screen does not correspond to a real screen. Letting the compositor pick.";
}
}

View file

@ -1,3 +1,5 @@
pragma ComponentBehavior: Bound
import QtQuick
///! Rectangle capable of clipping content inside its border.
@ -72,6 +74,12 @@ Item {
}
}
ShaderEffectSource {
id: shaderSource
hideSource: true
sourceItem: contentItemContainer
}
ShaderEffect {
id: shader
anchors.fill: root
@ -79,10 +87,6 @@ Item {
property Rectangle rect: rectangle
property color backgroundColor: "white"
property color borderColor: root.border.color
property ShaderEffectSource content: ShaderEffectSource {
hideSource: true
sourceItem: contentItemContainer
}
property ShaderEffectSource content: shaderSource
}
}

View file

@ -13,6 +13,7 @@
#include <qqmlengine.h>
#include <qqmlinfo.h>
#include <qqmllist.h>
#include <qquickgraphicsconfiguration.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qregion.h>
@ -147,6 +148,15 @@ void ProxyWindowBase::ensureQWindow() {
this->window = nullptr; // createQQuickWindow may indirectly reference this->window
this->window = this->createQQuickWindow();
this->window->setFormat(format);
// needed for vulkan dmabuf import, qt ignores these if not applicable
auto graphicsConfig = this->window->graphicsConfiguration();
graphicsConfig.setDeviceExtensions({
"VK_KHR_external_memory_fd",
"VK_EXT_external_memory_dma_buf",
"VK_EXT_image_drm_format_modifier",
});
this->window->setGraphicsConfiguration(graphicsConfig);
}
void ProxyWindowBase::createWindow() {
@ -213,6 +223,7 @@ void ProxyWindowBase::completeWindow() {
this->trySetHeight(this->implicitHeight());
this->setColor(this->mColor);
this->updateMask();
QQuickWindowPrivate::get(this->window)->updatesEnabled = this->mUpdatesEnabled;
// notify initial / post-connection geometry
emit this->xChanged();
@ -288,10 +299,16 @@ void ProxyWindowBase::setVisibleDirect(bool visible) {
this->bBackerVisibility = false;
this->deleteWindow();
}
} else if (this->window != nullptr) {
if (visible) this->polishItems();
this->window->setVisible(visible);
this->bBackerVisibility = visible;
} else {
if (visible && this->window == nullptr) {
this->createWindow();
}
if (this->window != nullptr) {
if (visible) this->polishItems();
this->window->setVisible(visible);
this->bBackerVisibility = visible;
}
}
}
@ -463,6 +480,19 @@ void ProxyWindowBase::setSurfaceFormat(QsSurfaceFormat format) {
emit this->surfaceFormatChanged();
}
bool ProxyWindowBase::updatesEnabled() const { return this->mUpdatesEnabled; }
void ProxyWindowBase::setUpdatesEnabled(bool updatesEnabled) {
if (updatesEnabled == this->mUpdatesEnabled) return;
this->mUpdatesEnabled = updatesEnabled;
if (this->window != nullptr) {
QQuickWindowPrivate::get(this->window)->updatesEnabled = updatesEnabled;
}
emit this->updatesEnabledChanged();
}
qreal ProxyWindowBase::devicePixelRatio() const {
if (this->window != nullptr) return this->window->devicePixelRatio();
if (this->mScreen != nullptr) return this->mScreen->devicePixelRatio();

View file

@ -57,6 +57,7 @@ class ProxyWindowBase: public Reloadable {
Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged);
Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged);
Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged);
Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled NOTIFY updatesEnabledChanged);
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
// clang-format on
Q_CLASSINFO("DefaultProperty", "data");
@ -140,6 +141,9 @@ public:
[[nodiscard]] QsSurfaceFormat surfaceFormat() const { return this->qsSurfaceFormat; }
void setSurfaceFormat(QsSurfaceFormat format);
[[nodiscard]] bool updatesEnabled() const;
void setUpdatesEnabled(bool updatesEnabled);
[[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT
[[nodiscard]] QQmlListProperty<QObject> data();
@ -163,6 +167,7 @@ signals:
void colorChanged();
void maskChanged();
void surfaceFormatChanged();
void updatesEnabledChanged();
void polished();
protected slots:
@ -187,6 +192,7 @@ protected:
ProxyWindowContentItem* mContentItem = nullptr;
bool reloadComplete = false;
bool ranLints = false;
bool mUpdatesEnabled = true;
QsSurfaceFormat qsSurfaceFormat;
QSurfaceFormat mSurfaceFormat;

View file

@ -127,6 +127,9 @@ void WindowInterface::setMask(PendingRegion* mask) const { this->proxyWindow()->
QsSurfaceFormat WindowInterface::surfaceFormat() const { return this->proxyWindow()->surfaceFormat(); };
void WindowInterface::setSurfaceFormat(QsSurfaceFormat format) const { this->proxyWindow()->setSurfaceFormat(format); };
bool WindowInterface::updatesEnabled() const { return this->proxyWindow()->updatesEnabled(); };
void WindowInterface::setUpdatesEnabled(bool updatesEnabled) const { this->proxyWindow()->setUpdatesEnabled(updatesEnabled); };
QQmlListProperty<QObject> WindowInterface::data() const { return this->proxyWindow()->data(); };
// clang-format on
@ -148,6 +151,7 @@ void WindowInterface::connectSignals() const {
QObject::connect(window, &ProxyWindowBase::colorChanged, this, &WindowInterface::colorChanged);
QObject::connect(window, &ProxyWindowBase::maskChanged, this, &WindowInterface::maskChanged);
QObject::connect(window, &ProxyWindowBase::surfaceFormatChanged, this, &WindowInterface::surfaceFormatChanged);
QObject::connect(window, &ProxyWindowBase::updatesEnabledChanged, this, &WindowInterface::updatesEnabledChanged);
// clang-format on
}

View file

@ -143,6 +143,12 @@ class WindowInterface: public Reloadable {
///
/// > [!NOTE] The surface format cannot be changed after the window is created.
Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged);
/// If the window should receive render updates. Defaults to true.
///
/// When set to false, the window will not re-render in response to animations
/// or other visual updates from other windows. This is useful for static windows
/// such as wallpapers that do not need to update frequently, saving GPU cycles.
Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled NOTIFY updatesEnabledChanged);
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
// clang-format on
Q_CLASSINFO("DefaultProperty", "data");
@ -231,6 +237,9 @@ public:
[[nodiscard]] QsSurfaceFormat surfaceFormat() const;
void setSurfaceFormat(QsSurfaceFormat format) const;
[[nodiscard]] bool updatesEnabled() const;
void setUpdatesEnabled(bool updatesEnabled) const;
[[nodiscard]] QQmlListProperty<QObject> data() const;
static QsWindowAttached* qmlAttachedProperties(QObject* object);
@ -258,6 +267,7 @@ signals:
void colorChanged();
void maskChanged();
void surfaceFormatChanged();
void updatesEnabledChanged();
protected:
void connectSignals() const;