Compare commits

...

7 commits

Author SHA1 Message Date
Carson Powers
cddb4f061b
build: fix lint-staged to ignore deleted files
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-24 01:43:02 -08:00
outfoxxed
6e17efab83
wayland/screencopy: enable vulkan dmabuf support on session locks
Also reformat dmabuf
2026-02-24 00:05:20 -08:00
bbedward
36517a2c10
services/pipewire: manage default objs using normal qt properties
Fixes use after free bugs due to pointer mismatches in destructors.
Drops SimpleObjectHandle.
2026-02-23 23:17:42 -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
18 changed files with 554 additions and 92 deletions

View file

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

View file

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

View file

@ -13,7 +13,7 @@ lint-changed:
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }} git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
lint-staged: lint-staged:
git diff --staged --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }} git diff --staged --name-only --diff-filter=d HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
configure target='debug' *FLAGS='': configure target='debug' *FLAGS='':
cmake -GNinja -B {{builddir}} \ cmake -GNinja -B {{builddir}} \

View file

@ -25,6 +25,7 @@ set shell id.
- Added support for IPC signal listeners. - Added support for IPC signal listeners.
- Added Quickshell version checking and version gated preprocessing. - 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 a way to detect if an icon is from the system icon theme or not.
- Added vulkan support to screencopy.
## Other Changes ## Other Changes
@ -46,7 +47,10 @@ set shell id.
- Fixed asynchronous loaders not working before window creation. - Fixed asynchronous loaders not working before window creation.
- Fixed memory leak in IPC handlers. - Fixed memory leak in IPC handlers.
- Fixed ClippingRectangle related crashes. - Fixed ClippingRectangle related crashes.
- Fixed crashes when monitors are unplugged.
- Fixed crashes when default pipewire devices are lost.
## Packaging Changes ## Packaging Changes
`glib` and `polkit` have been added as dependencies when compiling with polkit agent support. `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, xorg,
libdrm, libdrm,
libgbm ? null, libgbm ? null,
vulkan-headers,
pipewire, pipewire,
pam, pam,
polkit, polkit,
@ -77,7 +78,7 @@
++ lib.optional withJemalloc jemalloc ++ lib.optional withJemalloc jemalloc
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland ++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
++ lib.optionals withWayland [ wayland wayland-protocols ] ++ 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 withX11 xorg.libxcb
++ lib.optional withPam pam ++ lib.optional withPam pam
++ lib.optional withPipewire pipewire ++ lib.optional withPipewire pipewire

View file

@ -251,37 +251,6 @@ public:
GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); } GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); }
}; };
template <auto member, auto destroyedSlot, auto changedSignal>
class SimpleObjectHandleOps {
using Traits = MemberPointerTraits<decltype(member)>;
public:
static bool setObject(Traits::Class* parent, Traits::Type value) {
if (value == parent->*member) return false;
if (parent->*member != nullptr) {
QObject::disconnect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
}
parent->*member = value;
if (value != nullptr) {
QObject::connect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
}
if constexpr (changedSignal != nullptr) {
emit(parent->*changedSignal)();
}
return true;
}
};
template <auto member, auto destroyedSlot, auto changedSignal = nullptr>
bool setSimpleObjectHandle(auto* parent, auto* value) {
return SimpleObjectHandleOps<member, destroyedSlot, changedSignal>::setObject(parent, value);
}
template <auto methodPtr> template <auto methodPtr>
class MethodFunctor { class MethodFunctor {
using PtrMeta = MemberPointerTraits<decltype(methodPtr)>; using PtrMeta = MemberPointerTraits<decltype(methodPtr)>;

View file

@ -12,7 +12,6 @@
#include <spa/utils/json.h> #include <spa/utils/json.h>
#include "../../core/logcat.hpp" #include "../../core/logcat.hpp"
#include "../../core/util.hpp"
#include "metadata.hpp" #include "metadata.hpp"
#include "node.hpp" #include "node.hpp"
#include "registry.hpp" #include "registry.hpp"
@ -138,32 +137,6 @@ void PwDefaultTracker::onNodeAdded(PwNode* node) {
} }
} }
void PwDefaultTracker::onNodeDestroyed(QObject* node) {
if (node == this->mDefaultSink) {
qCInfo(logDefaults) << "Default sink destroyed.";
this->mDefaultSink = nullptr;
emit this->defaultSinkChanged();
}
if (node == this->mDefaultSource) {
qCInfo(logDefaults) << "Default source destroyed.";
this->mDefaultSource = nullptr;
emit this->defaultSourceChanged();
}
if (node == this->mDefaultConfiguredSink) {
qCInfo(logDefaults) << "Default configured sink destroyed.";
this->mDefaultConfiguredSink = nullptr;
emit this->defaultConfiguredSinkChanged();
}
if (node == this->mDefaultConfiguredSource) {
qCInfo(logDefaults) << "Default configured source destroyed.";
this->mDefaultConfiguredSource = nullptr;
emit this->defaultConfiguredSourceChanged();
}
}
void PwDefaultTracker::changeConfiguredSink(PwNode* node) { void PwDefaultTracker::changeConfiguredSink(PwNode* node) {
if (node != nullptr) { if (node != nullptr) {
if (!node->type.testFlags(PwNodeType::AudioSink)) { if (!node->type.testFlags(PwNodeType::AudioSink)) {
@ -240,10 +213,23 @@ void PwDefaultTracker::setDefaultSink(PwNode* node) {
if (node == this->mDefaultSink) return; if (node == this->mDefaultSink) return;
qCInfo(logDefaults) << "Default sink changed to" << node; qCInfo(logDefaults) << "Default sink changed to" << node;
setSimpleObjectHandle< if (this->mDefaultSink != nullptr) {
&PwDefaultTracker::mDefaultSink, QObject::disconnect(this->mDefaultSink, nullptr, this, nullptr);
&PwDefaultTracker::onNodeDestroyed, }
&PwDefaultTracker::defaultSinkChanged>(this, node);
this->mDefaultSink = node;
if (node != nullptr) {
QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSinkDestroyed);
}
emit this->defaultSinkChanged();
}
void PwDefaultTracker::onDefaultSinkDestroyed() {
qCInfo(logDefaults) << "Default sink destroyed.";
this->mDefaultSink = nullptr;
emit this->defaultSinkChanged();
} }
void PwDefaultTracker::setDefaultSinkName(const QString& name) { void PwDefaultTracker::setDefaultSinkName(const QString& name) {
@ -257,10 +243,23 @@ void PwDefaultTracker::setDefaultSource(PwNode* node) {
if (node == this->mDefaultSource) return; if (node == this->mDefaultSource) return;
qCInfo(logDefaults) << "Default source changed to" << node; qCInfo(logDefaults) << "Default source changed to" << node;
setSimpleObjectHandle< if (this->mDefaultSource != nullptr) {
&PwDefaultTracker::mDefaultSource, QObject::disconnect(this->mDefaultSource, nullptr, this, nullptr);
&PwDefaultTracker::onNodeDestroyed, }
&PwDefaultTracker::defaultSourceChanged>(this, node);
this->mDefaultSource = node;
if (node != nullptr) {
QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSourceDestroyed);
}
emit this->defaultSourceChanged();
}
void PwDefaultTracker::onDefaultSourceDestroyed() {
qCInfo(logDefaults) << "Default source destroyed.";
this->mDefaultSource = nullptr;
emit this->defaultSourceChanged();
} }
void PwDefaultTracker::setDefaultSourceName(const QString& name) { void PwDefaultTracker::setDefaultSourceName(const QString& name) {
@ -274,10 +273,28 @@ void PwDefaultTracker::setDefaultConfiguredSink(PwNode* node) {
if (node == this->mDefaultConfiguredSink) return; if (node == this->mDefaultConfiguredSink) return;
qCInfo(logDefaults) << "Default configured sink changed to" << node; qCInfo(logDefaults) << "Default configured sink changed to" << node;
setSimpleObjectHandle< if (this->mDefaultConfiguredSink != nullptr) {
&PwDefaultTracker::mDefaultConfiguredSink, QObject::disconnect(this->mDefaultConfiguredSink, nullptr, this, nullptr);
&PwDefaultTracker::onNodeDestroyed, }
&PwDefaultTracker::defaultConfiguredSinkChanged>(this, node);
this->mDefaultConfiguredSink = node;
if (node != nullptr) {
QObject::connect(
node,
&QObject::destroyed,
this,
&PwDefaultTracker::onDefaultConfiguredSinkDestroyed
);
}
emit this->defaultConfiguredSinkChanged();
}
void PwDefaultTracker::onDefaultConfiguredSinkDestroyed() {
qCInfo(logDefaults) << "Default configured sink destroyed.";
this->mDefaultConfiguredSink = nullptr;
emit this->defaultConfiguredSinkChanged();
} }
void PwDefaultTracker::setDefaultConfiguredSinkName(const QString& name) { void PwDefaultTracker::setDefaultConfiguredSinkName(const QString& name) {
@ -291,10 +308,28 @@ void PwDefaultTracker::setDefaultConfiguredSource(PwNode* node) {
if (node == this->mDefaultConfiguredSource) return; if (node == this->mDefaultConfiguredSource) return;
qCInfo(logDefaults) << "Default configured source changed to" << node; qCInfo(logDefaults) << "Default configured source changed to" << node;
setSimpleObjectHandle< if (this->mDefaultConfiguredSource != nullptr) {
&PwDefaultTracker::mDefaultConfiguredSource, QObject::disconnect(this->mDefaultConfiguredSource, nullptr, this, nullptr);
&PwDefaultTracker::onNodeDestroyed, }
&PwDefaultTracker::defaultConfiguredSourceChanged>(this, node);
this->mDefaultConfiguredSource = node;
if (node != nullptr) {
QObject::connect(
node,
&QObject::destroyed,
this,
&PwDefaultTracker::onDefaultConfiguredSourceDestroyed
);
}
emit this->defaultConfiguredSourceChanged();
}
void PwDefaultTracker::onDefaultConfiguredSourceDestroyed() {
qCInfo(logDefaults) << "Default configured source destroyed.";
this->mDefaultConfiguredSource = nullptr;
emit this->defaultConfiguredSourceChanged();
} }
void PwDefaultTracker::setDefaultConfiguredSourceName(const QString& name) { void PwDefaultTracker::setDefaultConfiguredSourceName(const QString& name) {

View file

@ -44,7 +44,10 @@ private slots:
void onMetadataAdded(PwMetadata* metadata); void onMetadataAdded(PwMetadata* metadata);
void onMetadataProperty(const char* key, const char* type, const char* value); void onMetadataProperty(const char* key, const char* type, const char* value);
void onNodeAdded(PwNode* node); void onNodeAdded(PwNode* node);
void onNodeDestroyed(QObject* node); void onDefaultSinkDestroyed();
void onDefaultSourceDestroyed();
void onDefaultConfiguredSinkDestroyed();
void onDefaultConfiguredSourceDestroyed();
private: private:
void setDefaultSink(PwNode* node); void setDefaultSink(PwNode* node);

View file

@ -1,6 +1,8 @@
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl) pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl)
find_package(VulkanHeaders REQUIRED)
qt_add_library(quickshell-wayland-buffer STATIC qt_add_library(quickshell-wayland-buffer STATIC
manager.cpp manager.cpp
dmabuf.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") wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf")
target_link_libraries(quickshell-wayland-buffer PRIVATE 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 PkgConfig::dmabuf-deps
wlp-linux-dmabuf wlp-linux-dmabuf
Vulkan::Headers
) )
qs_pch(quickshell-wayland-buffer SET large) qs_pch(quickshell-wayland-buffer SET large)

View file

@ -14,6 +14,8 @@
#include <fcntl.h> #include <fcntl.h>
#include <gbm.h> #include <gbm.h>
#include <libdrm/drm_fourcc.h> #include <libdrm/drm_fourcc.h>
#include <private/qquickwindow_p.h>
#include <private/qrhivulkan_p.h>
#include <qcontainerfwd.h> #include <qcontainerfwd.h>
#include <qdebug.h> #include <qdebug.h>
#include <qlist.h> #include <qlist.h>
@ -24,12 +26,17 @@
#include <qpair.h> #include <qpair.h>
#include <qquickwindow.h> #include <qquickwindow.h>
#include <qscopedpointer.h> #include <qscopedpointer.h>
#include <qsgrendererinterface.h>
#include <qsgtexture_platform.h> #include <qsgtexture_platform.h>
#include <qtypes.h>
#include <qvulkanfunctions.h>
#include <qvulkaninstance.h>
#include <qwayland-linux-dmabuf-v1.h> #include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h> #include <qwaylandclientextension.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <vulkan/vulkan_core.h>
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#include <wayland-linux-dmabuf-v1-client-protocol.h> #include <wayland-linux-dmabuf-v1-client-protocol.h>
#include <wayland-util.h> #include <wayland-util.h>
@ -48,6 +55,36 @@ QS_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg)
LinuxDmabufManager* MANAGER = nullptr; // NOLINT 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 } // namespace
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) { 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() { void LinuxDmabufFormatSelection::ensureSorted() {
if (this->sorted) return; if (this->sorted) return;
auto beginIter = this->formats.begin(); 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) { auto argbIter = std::ranges::find_if(this->formats, [](const auto& format) {
return format.first == DRM_FORMAT_ARGB8888; 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; this->sorted = true;
} }
@ -532,6 +571,15 @@ bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const {
} }
WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) 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 = []() { static auto* glEGLImageTargetTexture2DOES = []() {
auto* fn = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>( auto* fn = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
eglGetProcAddress("glEGLImageTargetTexture2DOES") eglGetProcAddress("glEGLImageTargetTexture2DOES")
@ -662,6 +710,313 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
return tex; 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() { WlDmaBufferQSGTexture::~WlDmaBufferQSGTexture() {
auto* context = QOpenGLContext::currentContext(); auto* context = QOpenGLContext::currentContext();
auto* display = context->nativeInterface<QNativeInterface::QEGLContext>()->display(); auto* display = context->nativeInterface<QNativeInterface::QEGLContext>()->display();

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <array>
#include <cstdint> #include <cstdint>
#include <EGL/egl.h> #include <EGL/egl.h>
@ -12,9 +13,11 @@
#include <qsize.h> #include <qsize.h>
#include <qtclasshelpermacros.h> #include <qtclasshelpermacros.h>
#include <qtypes.h> #include <qtypes.h>
#include <qvulkanfunctions.h>
#include <qwayland-linux-dmabuf-v1.h> #include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h> #include <qwaylandclientextension.h>
#include <sys/types.h> #include <sys/types.h>
#include <vulkan/vulkan_core.h>
#include <wayland-linux-dmabuf-v1-client-protocol.h> #include <wayland-linux-dmabuf-v1-client-protocol.h>
#include <wayland-util.h> #include <wayland-util.h>
#include <xf86drm.h> #include <xf86drm.h>
@ -114,6 +117,36 @@ private:
friend class WlDmaBuffer; 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 { class WlDmaBuffer: public WlBuffer {
public: public:
~WlDmaBuffer() override; ~WlDmaBuffer() override;
@ -151,6 +184,9 @@ private:
friend class LinuxDmabufManager; friend class LinuxDmabufManager;
friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer); 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); QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);

View file

@ -9,6 +9,7 @@
#include <qqmlcomponent.h> #include <qqmlcomponent.h>
#include <qqmlengine.h> #include <qqmlengine.h>
#include <qqmllist.h> #include <qqmllist.h>
#include <qquickgraphicsconfiguration.h>
#include <qquickitem.h> #include <qquickitem.h>
#include <qquickwindow.h> #include <qquickwindow.h>
#include <qscreen.h> #include <qscreen.h>
@ -216,6 +217,15 @@ void WlSessionLockSurface::onReload(QObject* oldInstance) {
if (this->window == nullptr) { if (this->window == nullptr) {
this->window = new QQuickWindow(); 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()); this->mContentItem->setParentItem(this->window->contentItem());

View file

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

@ -13,6 +13,7 @@
#include <qqmlengine.h> #include <qqmlengine.h>
#include <qqmlinfo.h> #include <qqmlinfo.h>
#include <qqmllist.h> #include <qqmllist.h>
#include <qquickgraphicsconfiguration.h>
#include <qquickitem.h> #include <qquickitem.h>
#include <qquickwindow.h> #include <qquickwindow.h>
#include <qregion.h> #include <qregion.h>
@ -147,6 +148,15 @@ void ProxyWindowBase::ensureQWindow() {
this->window = nullptr; // createQQuickWindow may indirectly reference this->window this->window = nullptr; // createQQuickWindow may indirectly reference this->window
this->window = this->createQQuickWindow(); this->window = this->createQQuickWindow();
this->window->setFormat(format); 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() { void ProxyWindowBase::createWindow() {
@ -213,6 +223,7 @@ void ProxyWindowBase::completeWindow() {
this->trySetHeight(this->implicitHeight()); this->trySetHeight(this->implicitHeight());
this->setColor(this->mColor); this->setColor(this->mColor);
this->updateMask(); this->updateMask();
QQuickWindowPrivate::get(this->window)->updatesEnabled = this->mUpdatesEnabled;
// notify initial / post-connection geometry // notify initial / post-connection geometry
emit this->xChanged(); emit this->xChanged();
@ -469,6 +480,19 @@ void ProxyWindowBase::setSurfaceFormat(QsSurfaceFormat format) {
emit this->surfaceFormatChanged(); 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 { qreal ProxyWindowBase::devicePixelRatio() const {
if (this->window != nullptr) return this->window->devicePixelRatio(); if (this->window != nullptr) return this->window->devicePixelRatio();
if (this->mScreen != nullptr) return this->mScreen->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(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged);
Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged); Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged);
Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged); 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); Q_PROPERTY(QQmlListProperty<QObject> data READ data);
// clang-format on // clang-format on
Q_CLASSINFO("DefaultProperty", "data"); Q_CLASSINFO("DefaultProperty", "data");
@ -140,6 +141,9 @@ public:
[[nodiscard]] QsSurfaceFormat surfaceFormat() const { return this->qsSurfaceFormat; } [[nodiscard]] QsSurfaceFormat surfaceFormat() const { return this->qsSurfaceFormat; }
void setSurfaceFormat(QsSurfaceFormat format); void setSurfaceFormat(QsSurfaceFormat format);
[[nodiscard]] bool updatesEnabled() const;
void setUpdatesEnabled(bool updatesEnabled);
[[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT [[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT
[[nodiscard]] QQmlListProperty<QObject> data(); [[nodiscard]] QQmlListProperty<QObject> data();
@ -163,6 +167,7 @@ signals:
void colorChanged(); void colorChanged();
void maskChanged(); void maskChanged();
void surfaceFormatChanged(); void surfaceFormatChanged();
void updatesEnabledChanged();
void polished(); void polished();
protected slots: protected slots:
@ -187,6 +192,7 @@ protected:
ProxyWindowContentItem* mContentItem = nullptr; ProxyWindowContentItem* mContentItem = nullptr;
bool reloadComplete = false; bool reloadComplete = false;
bool ranLints = false; bool ranLints = false;
bool mUpdatesEnabled = true;
QsSurfaceFormat qsSurfaceFormat; QsSurfaceFormat qsSurfaceFormat;
QSurfaceFormat mSurfaceFormat; QSurfaceFormat mSurfaceFormat;

View file

@ -127,6 +127,9 @@ void WindowInterface::setMask(PendingRegion* mask) const { this->proxyWindow()->
QsSurfaceFormat WindowInterface::surfaceFormat() const { return this->proxyWindow()->surfaceFormat(); }; QsSurfaceFormat WindowInterface::surfaceFormat() const { return this->proxyWindow()->surfaceFormat(); };
void WindowInterface::setSurfaceFormat(QsSurfaceFormat format) const { this->proxyWindow()->setSurfaceFormat(format); }; 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(); }; QQmlListProperty<QObject> WindowInterface::data() const { return this->proxyWindow()->data(); };
// clang-format on // clang-format on
@ -148,6 +151,7 @@ void WindowInterface::connectSignals() const {
QObject::connect(window, &ProxyWindowBase::colorChanged, this, &WindowInterface::colorChanged); QObject::connect(window, &ProxyWindowBase::colorChanged, this, &WindowInterface::colorChanged);
QObject::connect(window, &ProxyWindowBase::maskChanged, this, &WindowInterface::maskChanged); QObject::connect(window, &ProxyWindowBase::maskChanged, this, &WindowInterface::maskChanged);
QObject::connect(window, &ProxyWindowBase::surfaceFormatChanged, this, &WindowInterface::surfaceFormatChanged); QObject::connect(window, &ProxyWindowBase::surfaceFormatChanged, this, &WindowInterface::surfaceFormatChanged);
QObject::connect(window, &ProxyWindowBase::updatesEnabledChanged, this, &WindowInterface::updatesEnabledChanged);
// clang-format on // 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. /// > [!NOTE] The surface format cannot be changed after the window is created.
Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged); 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); Q_PROPERTY(QQmlListProperty<QObject> data READ data);
// clang-format on // clang-format on
Q_CLASSINFO("DefaultProperty", "data"); Q_CLASSINFO("DefaultProperty", "data");
@ -231,6 +237,9 @@ public:
[[nodiscard]] QsSurfaceFormat surfaceFormat() const; [[nodiscard]] QsSurfaceFormat surfaceFormat() const;
void setSurfaceFormat(QsSurfaceFormat format) const; void setSurfaceFormat(QsSurfaceFormat format) const;
[[nodiscard]] bool updatesEnabled() const;
void setUpdatesEnabled(bool updatesEnabled) const;
[[nodiscard]] QQmlListProperty<QObject> data() const; [[nodiscard]] QQmlListProperty<QObject> data() const;
static QsWindowAttached* qmlAttachedProperties(QObject* object); static QsWindowAttached* qmlAttachedProperties(QObject* object);
@ -258,6 +267,7 @@ signals:
void colorChanged(); void colorChanged();
void maskChanged(); void maskChanged();
void surfaceFormatChanged(); void surfaceFormatChanged();
void updatesEnabledChanged();
protected: protected:
void connectSignals() const; void connectSignals() const;