From 158db16b931d04b43ec84748ee49390ea9f7c3f8 Mon Sep 17 00:00:00 2001 From: Bryan Paradis Date: Wed, 18 Feb 2026 07:36:21 -0800 Subject: [PATCH 1/7] wayland: check screen isPlaceholder and if wl_output is null Fixes crashes on disconnected monitors --- changelog/next.md | 1 + src/wayland/session_lock/surface.cpp | 2 +- src/wayland/wlr_layershell/surface.cpp | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/changelog/next.md b/changelog/next.md index 66f87c1..042a6ea 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -46,6 +46,7 @@ set shell id. - 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 diff --git a/src/wayland/session_lock/surface.cpp b/src/wayland/session_lock/surface.cpp index 6ec4eb6..c73f459 100644 --- a/src/wayland/session_lock/surface.cpp +++ b/src/wayland/session_lock/surface.cpp @@ -28,7 +28,7 @@ QSWaylandSessionLockSurface::QSWaylandSessionLockSurface(QtWaylandClient::QWayla wl_output* output = nullptr; // NOLINT (include) auto* waylandScreen = dynamic_cast(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"; diff --git a/src/wayland/wlr_layershell/surface.cpp b/src/wayland/wlr_layershell/surface.cpp index 3c71ff9..4a5015e 100644 --- a/src/wayland/wlr_layershell/surface.cpp +++ b/src/wayland/wlr_layershell/surface.cpp @@ -143,11 +143,11 @@ LayerSurface::LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWayla auto* waylandScreen = dynamic_cast(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."; } } From a99519c3adbc9eb9a80b32cde7264e9f147e3416 Mon Sep 17 00:00:00 2001 From: reakjra Date: Mon, 9 Feb 2026 18:20:14 +0100 Subject: [PATCH 2/7] wayland/screencopy: support dmabufs in vulkan mode --- .github/workflows/build.yml | 1 + BUILD.md | 1 + changelog/next.md | 2 + default.nix | 3 +- src/wayland/buffer/CMakeLists.txt | 5 +- src/wayland/buffer/dmabuf.cpp | 319 ++++++++++++++++++++++++++++++ src/wayland/buffer/dmabuf.hpp | 36 ++++ src/window/proxywindow.cpp | 10 + 8 files changed, 375 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 66c3691..8d19f58 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,6 +50,7 @@ jobs: wayland-protocols \ wayland \ libdrm \ + vulkan-headers \ libxcb \ libpipewire \ cli11 \ diff --git a/BUILD.md b/BUILD.md index fdea27e..c9459b5 100644 --- a/BUILD.md +++ b/BUILD.md @@ -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] diff --git a/changelog/next.md b/changelog/next.md index 042a6ea..7180d53 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -25,6 +25,7 @@ set shell id. - 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 @@ -51,3 +52,4 @@ set shell id. ## 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). diff --git a/default.nix b/default.nix index 0b6f303..7783774 100644 --- a/default.nix +++ b/default.nix @@ -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 diff --git a/src/wayland/buffer/CMakeLists.txt b/src/wayland/buffer/CMakeLists.txt index f80c53a..a8f2d2d 100644 --- a/src/wayland/buffer/CMakeLists.txt +++ b/src/wayland/buffer/CMakeLists.txt @@ -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::WaylandClient Qt::WaylandClientPrivate wayland-client PkgConfig::dmabuf-deps wlp-linux-dmabuf + Vulkan::Headers ) qs_pch(quickshell-wayland-buffer SET large) diff --git a/src/wayland/buffer/dmabuf.cpp b/src/wayland/buffer/dmabuf.cpp index e51a1d0..89c9108 100644 --- a/src/wayland/buffer/dmabuf.cpp +++ b/src/wayland/buffer/dmabuf.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -24,12 +25,17 @@ #include #include #include +#include #include +#include +#include #include #include #include #include #include +#include +#include #include #include #include @@ -48,6 +54,25 @@ 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) +} + } // namespace QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) { @@ -532,6 +557,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( eglGetProcAddress("glEGLImageTargetTexture2DOES") @@ -662,6 +696,291 @@ 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(ri->getResource(window, QSGRendererInterface::DeviceResource)); + auto* vkPhysDevicePtr = static_cast( + 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( + 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 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(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( + 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 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(image), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + static_cast(vkFormat), + QSize(static_cast(this->width), static_cast(this->height)), + {} + ); + + 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()->display(); diff --git a/src/wayland/buffer/dmabuf.hpp b/src/wayland/buffer/dmabuf.hpp index 1e4ef1a..ffe5d02 100644 --- a/src/wayland/buffer/dmabuf.hpp +++ b/src/wayland/buffer/dmabuf.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -12,9 +13,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -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); diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index 4423547..b4f79da 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -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() { From 2cf57f43d5f2a5b139d1f1702c83e126e17f27f8 Mon Sep 17 00:00:00 2001 From: bbedward Date: Thu, 19 Feb 2026 12:14:36 -0500 Subject: [PATCH 3/7] core/proxywindow: expose updatesEnabled property --- src/window/proxywindow.cpp | 14 ++++++++++++++ src/window/proxywindow.hpp | 6 ++++++ src/window/windowinterface.cpp | 4 ++++ src/window/windowinterface.hpp | 10 ++++++++++ 4 files changed, 34 insertions(+) diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index b4f79da..62126bd 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -223,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(); @@ -479,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(); diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index 86d66f8..aec821e 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -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 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 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; diff --git a/src/window/windowinterface.cpp b/src/window/windowinterface.cpp index 8917f12..e41afc2 100644 --- a/src/window/windowinterface.cpp +++ b/src/window/windowinterface.cpp @@ -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 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 } diff --git a/src/window/windowinterface.hpp b/src/window/windowinterface.hpp index 9e917b9..6f3db20 100644 --- a/src/window/windowinterface.hpp +++ b/src/window/windowinterface.hpp @@ -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 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 data() const; static QsWindowAttached* qmlAttachedProperties(QObject* object); @@ -258,6 +267,7 @@ signals: void colorChanged(); void maskChanged(); void surfaceFormatChanged(); + void updatesEnabledChanged(); protected: void connectSignals() const; From c3c3e2ca251a430dbe1b2d46ab0af4e5ca82c7e8 Mon Sep 17 00:00:00 2001 From: reakjra Date: Mon, 23 Feb 2026 18:28:01 +0100 Subject: [PATCH 4/7] 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. --- src/wayland/buffer/CMakeLists.txt | 2 +- src/wayland/buffer/dmabuf.cpp | 65 +++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/wayland/buffer/CMakeLists.txt b/src/wayland/buffer/CMakeLists.txt index a8f2d2d..15818fc 100644 --- a/src/wayland/buffer/CMakeLists.txt +++ b/src/wayland/buffer/CMakeLists.txt @@ -12,7 +12,7 @@ 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::QuickPrivate 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 diff --git a/src/wayland/buffer/dmabuf.cpp b/src/wayland/buffer/dmabuf.cpp index 89c9108..7d17884 100644 --- a/src/wayland/buffer/dmabuf.cpp +++ b/src/wayland/buffer/dmabuf.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,19 @@ VkFormat drmFormatToVkFormat(uint32_t drmFormat) { // 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) { @@ -106,25 +120,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; } @@ -946,6 +962,33 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) co {} ); + // 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(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, From 36517a2c10d206bbde30f6a43e0002b3c3ce139f Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 13 Feb 2026 17:54:43 -0500 Subject: [PATCH 5/7] services/pipewire: manage default objs using normal qt properties Fixes use after free bugs due to pointer mismatches in destructors. Drops SimpleObjectHandle. --- changelog/next.md | 1 + src/core/util.hpp | 31 -------- src/services/pipewire/defaults.cpp | 121 +++++++++++++++++++---------- src/services/pipewire/defaults.hpp | 5 +- 4 files changed, 83 insertions(+), 75 deletions(-) diff --git a/changelog/next.md b/changelog/next.md index 7180d53..b9000c2 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -48,6 +48,7 @@ set shell id. - Fixed memory leak in IPC handlers. - Fixed ClippingRectangle related crashes. - Fixed crashes when monitors are unplugged. +- Fixed crashes when default pipewire devices are lost. ## Packaging Changes diff --git a/src/core/util.hpp b/src/core/util.hpp index 3b86d28..bb8dd85 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -251,37 +251,6 @@ public: GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); } }; -template -class SimpleObjectHandleOps { - using Traits = MemberPointerTraits; - -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 -bool setSimpleObjectHandle(auto* parent, auto* value) { - return SimpleObjectHandleOps::setObject(parent, value); -} - template class MethodFunctor { using PtrMeta = MemberPointerTraits; diff --git a/src/services/pipewire/defaults.cpp b/src/services/pipewire/defaults.cpp index 02463f4..7a24a65 100644 --- a/src/services/pipewire/defaults.cpp +++ b/src/services/pipewire/defaults.cpp @@ -12,7 +12,6 @@ #include #include "../../core/logcat.hpp" -#include "../../core/util.hpp" #include "metadata.hpp" #include "node.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) { if (node != nullptr) { if (!node->type.testFlags(PwNodeType::AudioSink)) { @@ -240,10 +213,23 @@ void PwDefaultTracker::setDefaultSink(PwNode* node) { if (node == this->mDefaultSink) return; qCInfo(logDefaults) << "Default sink changed to" << node; - setSimpleObjectHandle< - &PwDefaultTracker::mDefaultSink, - &PwDefaultTracker::onNodeDestroyed, - &PwDefaultTracker::defaultSinkChanged>(this, node); + if (this->mDefaultSink != nullptr) { + QObject::disconnect(this->mDefaultSink, nullptr, this, nullptr); + } + + 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) { @@ -257,10 +243,23 @@ void PwDefaultTracker::setDefaultSource(PwNode* node) { if (node == this->mDefaultSource) return; qCInfo(logDefaults) << "Default source changed to" << node; - setSimpleObjectHandle< - &PwDefaultTracker::mDefaultSource, - &PwDefaultTracker::onNodeDestroyed, - &PwDefaultTracker::defaultSourceChanged>(this, node); + if (this->mDefaultSource != nullptr) { + QObject::disconnect(this->mDefaultSource, nullptr, this, nullptr); + } + + 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) { @@ -274,10 +273,28 @@ void PwDefaultTracker::setDefaultConfiguredSink(PwNode* node) { if (node == this->mDefaultConfiguredSink) return; qCInfo(logDefaults) << "Default configured sink changed to" << node; - setSimpleObjectHandle< - &PwDefaultTracker::mDefaultConfiguredSink, - &PwDefaultTracker::onNodeDestroyed, - &PwDefaultTracker::defaultConfiguredSinkChanged>(this, node); + if (this->mDefaultConfiguredSink != nullptr) { + QObject::disconnect(this->mDefaultConfiguredSink, nullptr, this, nullptr); + } + + 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) { @@ -291,10 +308,28 @@ void PwDefaultTracker::setDefaultConfiguredSource(PwNode* node) { if (node == this->mDefaultConfiguredSource) return; qCInfo(logDefaults) << "Default configured source changed to" << node; - setSimpleObjectHandle< - &PwDefaultTracker::mDefaultConfiguredSource, - &PwDefaultTracker::onNodeDestroyed, - &PwDefaultTracker::defaultConfiguredSourceChanged>(this, node); + if (this->mDefaultConfiguredSource != nullptr) { + QObject::disconnect(this->mDefaultConfiguredSource, nullptr, this, nullptr); + } + + 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) { diff --git a/src/services/pipewire/defaults.hpp b/src/services/pipewire/defaults.hpp index 591c4fd..f31669e 100644 --- a/src/services/pipewire/defaults.hpp +++ b/src/services/pipewire/defaults.hpp @@ -44,7 +44,10 @@ private slots: void onMetadataAdded(PwMetadata* metadata); void onMetadataProperty(const char* key, const char* type, const char* value); void onNodeAdded(PwNode* node); - void onNodeDestroyed(QObject* node); + void onDefaultSinkDestroyed(); + void onDefaultSourceDestroyed(); + void onDefaultConfiguredSinkDestroyed(); + void onDefaultConfiguredSourceDestroyed(); private: void setDefaultSink(PwNode* node); From 6e17efab83d3a5ad5d6e59bc08d26095c6660502 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Mon, 23 Feb 2026 23:03:48 -0800 Subject: [PATCH 6/7] wayland/screencopy: enable vulkan dmabuf support on session locks Also reformat dmabuf --- src/wayland/buffer/dmabuf.cpp | 27 ++++++++++----------------- src/wayland/session_lock.cpp | 10 ++++++++++ 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/wayland/buffer/dmabuf.cpp b/src/wayland/buffer/dmabuf.cpp index 7d17884..ed9dbeb 100644 --- a/src/wayland/buffer/dmabuf.cpp +++ b/src/wayland/buffer/dmabuf.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -80,10 +80,8 @@ bool drmFormatHasAlpha(uint32_t drmFormat) { case DRM_FORMAT_ABGR8888: case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_ABGR2101010: - case DRM_FORMAT_ABGR16161616F: - return true; - default: - return false; + case DRM_FORMAT_ABGR16161616F: return true; + default: return false; } } @@ -818,7 +816,8 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) co // 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) + 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 @@ -909,12 +908,12 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) co // find the graphics queue family index for the ownrship transfer. uint32_t graphicsQueueFamily = 0; uint32_t queueFamilyCount = 0; - instFuncs->vkGetPhysicalDeviceQueueFamilyProperties( - physDevice, &queueFamilyCount, nullptr - ); + instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(physDevice, &queueFamilyCount, nullptr); std::vector queueFamilies(queueFamilyCount); instFuncs->vkGetPhysicalDeviceQueueFamilyProperties( - physDevice, &queueFamilyCount, queueFamilies.data() + physDevice, + &queueFamilyCount, + queueFamilies.data() ); for (uint32_t i = 0; i < queueFamilyCount; ++i) { if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { @@ -989,13 +988,7 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) co } } - auto* tex = new WlDmaBufferVulkanQSGTexture( - devFuncs, - device, - image, - memory, - qsgTexture - ); + auto* tex = new WlDmaBufferVulkanQSGTexture(devFuncs, device, image, memory, qsgTexture); qCDebug(logDmabuf) << "Created WlDmaBufferVulkanQSGTexture" << tex << "from" << this; return tex; } diff --git a/src/wayland/session_lock.cpp b/src/wayland/session_lock.cpp index d5a3e53..2ebe3fd 100644 --- a/src/wayland/session_lock.cpp +++ b/src/wayland/session_lock.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -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()); From cddb4f061bab495f4473ca5f2c571b6c710efef7 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Fri, 6 Feb 2026 17:25:50 -0600 Subject: [PATCH 7/7] build: fix lint-staged to ignore deleted files --- Justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Justfile b/Justfile index 2d6377e..801eb2a 100644 --- a/Justfile +++ b/Justfile @@ -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") }} 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='': cmake -GNinja -B {{builddir}} \