diff --git a/.gitignore b/.gitignore index 33419bc..2905419 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode/ build/ -protocols/ \ No newline at end of file +protocols/*.h +protocols/*.c \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 6aac750..3ff8e19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ message(STATUS "Checking deps...") find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) find_package(OpenGL REQUIRED) -pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon cairo pangocairo pam) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon cairo pangocairo pam libdrm gbm) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") add_executable(hyprlock ${SRCFILES}) @@ -75,6 +75,8 @@ protocol("staging/cursor-shape/cursor-shape-v1.xml" "cursor-shape-v1" false) protocol("unstable/tablet/tablet-unstable-v2.xml" "tablet-unstable-v2" false) protocol("staging/fractional-scale/fractional-scale-v1.xml" "fractional-scale-v1" false) protocol("stable/viewporter/viewporter.xml" "viewporter" false) +protocol("protocols/wlr-screencopy-unstable-v1.xml" "wlr-screencopy-unstable-v1" true) +protocol("unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml" "linux-dmabuf-unstable-v1" false) # Installation install(TARGETS hyprlock) diff --git a/protocols/wlr-screencopy-unstable-v1.xml b/protocols/wlr-screencopy-unstable-v1.xml new file mode 100644 index 0000000..b60ae2c --- /dev/null +++ b/protocols/wlr-screencopy-unstable-v1.xml @@ -0,0 +1,232 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Andri Yngvason + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to ask the compositor to copy part of the + screen content to a client buffer. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This object is a manager which offers requests to start capturing from a + source. + + + + + Capture the next frame of an entire output. + + + + + + + + + Capture the next frame of an output's region. + + The region is given in output logical coordinates, see + xdg_output.logical_size. The region will be clipped to the output's + extents. + + + + + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single frame. + + When created, a series of buffer events will be sent, each representing a + supported buffer type. The "buffer_done" event is sent afterwards to + indicate that all supported buffer types have been enumerated. The client + will then be able to send a "copy" request. If the capture is successful, + the compositor will send a "flags" followed by a "ready" event. + + For objects version 2 or lower, wl_shm buffers are always supported, ie. + the "buffer" event is guaranteed to be sent. + + If the capture failed, the "failed" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "failed" event is received, the client should + destroy the frame. + + + + + Provides information about wl_shm buffer parameters that need to be + used for this frame. This event is sent once after the frame is created + if wl_shm buffers are supported. + + + + + + + + + + Copy the frame to the supplied buffer. The buffer must have a the + correct size, see zwlr_screencopy_frame_v1.buffer and + zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a + supported format. + + If the frame is successfully copied, a "flags" and a "ready" events are + sent. Otherwise, a "failed" event is sent. + + + + + + + + + + + + + + + + Provides flags about the frame. This event is sent once before the + "ready" event. + + + + + + + Called as soon as the frame is copied, indicating it is available + for reading. This event includes the time at which presentation happened + at. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy the object. + + + + + + + + + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client should destroy the object. + + + + + + Destroys the frame. This request can be sent at any time by the client. + + + + + + + Same as copy, except it waits until there is damage to copy. + + + + + + + This event is sent right before the ready event when copy_with_damage is + requested. It may be generated multiple times for each copy_with_damage + request. + + The arguments describe a box around an area that has changed since the + last copy request that was derived from the current screencopy manager + instance. + + The union of all regions received between the call to copy_with_damage + and a ready event is the total damage since the prior ready event. + + + + + + + + + + + Provides information about linux-dmabuf buffer parameters that need to + be used for this frame. This event is sent once after the frame is + created if linux-dmabuf buffers are supported. + + + + + + + + + This event is sent once after all buffer events have been sent. + + The client should proceed to create a buffer of one of the supported + types, and send a "copy" request. + + + + diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index fcbdfaf..35f8617 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include CHyprlock::CHyprlock(const std::string& wlDisplay) { m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str()); @@ -24,8 +27,6 @@ CHyprlock::CHyprlock(const std::string& wlDisplay) { if (!m_pXKBContext) Debug::log(ERR, "Failed to create xkb context"); - g_pRenderer = std::make_unique(); - const auto GRACE = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:grace"); m_tGraceEnds = **GRACE ? std::chrono::system_clock::now() + std::chrono::seconds(**GRACE) : std::chrono::system_clock::from_time_t(0); } @@ -42,6 +43,177 @@ inline const wl_seat_listener seatListener = { // end wl_seat +// dmabuf + +static void handleDMABUFFormat(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format) { + ; +} + +static void handleDMABUFModifier(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { + g_pHyprlock->dma.dmabufMods.push_back({format, (((uint64_t)modifier_hi) << 32) | modifier_lo}); +} + +inline const zwp_linux_dmabuf_v1_listener dmabufListener = { + .format = handleDMABUFFormat, + .modifier = handleDMABUFModifier, +}; + +static void dmabufFeedbackMainDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) { + Debug::log(LOG, "[core] dmabufFeedbackMainDevice"); + + RASSERT(!g_pHyprlock->dma.gbm, "double dmabuf feedback"); + + dev_t device; + assert(device_arr->size == sizeof(device)); + memcpy(&device, device_arr->data, sizeof(device)); + + drmDevice* drmDev; + if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { + Debug::log(WARN, "[dmabuf] unable to open main device?"); + exit(1); + } + + g_pHyprlock->dma.gbmDevice = g_pHyprlock->createGBMDevice(drmDev); +} + +static void dmabufFeedbackFormatTable(void* data, zwp_linux_dmabuf_feedback_v1* feedback, int fd, uint32_t size) { + Debug::log(TRACE, "[core] dmabufFeedbackFormatTable"); + + g_pHyprlock->dma.dmabufMods.clear(); + + g_pHyprlock->dma.formatTable = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + + if (g_pHyprlock->dma.formatTable == MAP_FAILED) { + Debug::log(ERR, "[core] format table failed to mmap"); + g_pHyprlock->dma.formatTable = nullptr; + g_pHyprlock->dma.formatTableSize = 0; + return; + } + + g_pHyprlock->dma.formatTableSize = size; +} + +static void dmabufFeedbackDone(void* data, zwp_linux_dmabuf_feedback_v1* feedback) { + Debug::log(TRACE, "[core] dmabufFeedbackDone"); + + if (g_pHyprlock->dma.formatTable) + munmap(g_pHyprlock->dma.formatTable, g_pHyprlock->dma.formatTableSize); + + g_pHyprlock->dma.formatTable = nullptr; + g_pHyprlock->dma.formatTableSize = 0; +} + +static void dmabufFeedbackTrancheTargetDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) { + Debug::log(TRACE, "[core] dmabufFeedbackTrancheTargetDevice"); + + dev_t device; + assert(device_arr->size == sizeof(device)); + memcpy(&device, device_arr->data, sizeof(device)); + + drmDevice* drmDev; + if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) + return; + + if (g_pHyprlock->dma.gbmDevice) { + drmDevice* drmDevRenderer = NULL; + drmGetDevice2(gbm_device_get_fd(g_pHyprlock->dma.gbmDevice), /* flags */ 0, &drmDevRenderer); + g_pHyprlock->dma.deviceUsed = drmDevicesEqual(drmDevRenderer, drmDev); + } else { + g_pHyprlock->dma.gbmDevice = g_pHyprlock->createGBMDevice(drmDev); + g_pHyprlock->dma.deviceUsed = g_pHyprlock->dma.gbm; + } +} + +static void dmabufFeedbackTrancheFlags(void* data, zwp_linux_dmabuf_feedback_v1* feedback, uint32_t flags) { + ; +} + +static void dmabufFeedbackTrancheFormats(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* indices) { + Debug::log(TRACE, "[core] dmabufFeedbackTrancheFormats"); + + if (!g_pHyprlock->dma.deviceUsed || !g_pHyprlock->dma.formatTable) + return; + + struct fm_entry { + uint32_t format; + uint32_t padding; + uint64_t modifier; + }; + // An entry in the table has to be 16 bytes long + assert(sizeof(fm_entry) == 16); + + uint32_t n_modifiers = g_pHyprlock->dma.formatTableSize / sizeof(fm_entry); + fm_entry* fm_entry = (struct fm_entry*)g_pHyprlock->dma.formatTable; + uint16_t* idx; + + for (idx = (uint16_t*)indices->data; (const char*)idx < (const char*)indices->data + indices->size; idx++) { + if (*idx >= n_modifiers) + continue; + + g_pHyprlock->dma.dmabufMods.push_back({(fm_entry + *idx)->format, (fm_entry + *idx)->modifier}); + } +} + +static void dmabufFeedbackTrancheDone(void* data, struct zwp_linux_dmabuf_feedback_v1* zwp_linux_dmabuf_feedback_v1) { + Debug::log(TRACE, "[core] dmabufFeedbackTrancheDone"); + + g_pHyprlock->dma.deviceUsed = false; +} + +inline const zwp_linux_dmabuf_feedback_v1_listener dmabufFeedbackListener = { + .done = dmabufFeedbackDone, + .format_table = dmabufFeedbackFormatTable, + .main_device = dmabufFeedbackMainDevice, + .tranche_done = dmabufFeedbackTrancheDone, + .tranche_target_device = dmabufFeedbackTrancheTargetDevice, + .tranche_formats = dmabufFeedbackTrancheFormats, + .tranche_flags = dmabufFeedbackTrancheFlags, +}; + +static char* gbm_find_render_node(drmDevice* device) { + drmDevice* devices[64]; + char* render_node = NULL; + + int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0])); + for (int i = 0; i < n; ++i) { + drmDevice* dev = devices[i]; + if (device && !drmDevicesEqual(device, dev)) { + continue; + } + if (!(dev->available_nodes & (1 << DRM_NODE_RENDER))) + continue; + + render_node = strdup(dev->nodes[DRM_NODE_RENDER]); + break; + } + + drmFreeDevices(devices, n); + return render_node; +} + +gbm_device* CHyprlock::createGBMDevice(drmDevice* dev) { + char* renderNode = gbm_find_render_node(dev); + + if (!renderNode) { + Debug::log(ERR, "[core] Couldn't find a render node"); + return nullptr; + } + + Debug::log(TRACE, "[core] createGBMDevice: render node {}", renderNode); + + int fd = open(renderNode, O_RDWR | O_CLOEXEC); + if (fd < 0) { + Debug::log(ERR, "[core] couldn't open render node"); + free(renderNode); + return NULL; + } + + free(renderNode); + return gbm_create_device(fd); +} + +// end dmabuf + // wl_registry static void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { @@ -75,11 +247,9 @@ void CHyprlock::onGlobal(void* data, struct wl_registry* registry, uint32_t name Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wl_output_interface.name) { m_vOutputs.emplace_back(std::make_unique((wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version), name)); - Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wp_cursor_shape_manager_v1_interface.name) { m_pCursorShape = std::make_unique((wp_cursor_shape_manager_v1*)wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, version)); - Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wl_compositor_interface.name) { m_sWaylandState.compositor = (wl_compositor*)wl_registry_bind(registry, name, &wl_compositor_interface, version); @@ -90,6 +260,19 @@ void CHyprlock::onGlobal(void* data, struct wl_registry* registry, uint32_t name } else if (IFACE == wp_viewporter_interface.name) { m_sWaylandState.viewporter = (wp_viewporter*)wl_registry_bind(registry, name, &wp_viewporter_interface, version); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); + } else if (IFACE == zwp_linux_dmabuf_v1_interface.name) { + if (version < 4) { + Debug::log(ERR, "cannot use linux_dmabuf with ver < 4"); + return; + } + + dma.linuxDmabuf = wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version); + dma.linuxDmabufFeedback = zwp_linux_dmabuf_v1_get_default_feedback((zwp_linux_dmabuf_v1*)dma.linuxDmabuf); + zwp_linux_dmabuf_feedback_v1_add_listener((zwp_linux_dmabuf_feedback_v1*)dma.linuxDmabufFeedback, &dmabufFeedbackListener, nullptr); + Debug::log(LOG, " > Bound to {} v{}", IFACE, version); + } else if (IFACE == zwlr_screencopy_manager_v1_interface.name) { + m_sWaylandState.screencopy = (zwlr_screencopy_manager_v1*)wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, version); + Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } } @@ -115,6 +298,8 @@ void CHyprlock::run() { // gather info about monitors wl_display_roundtrip(m_sWaylandState.display); + g_pRenderer = std::make_unique(); + lockSession(); pollfd pollfds[] = { @@ -244,8 +429,10 @@ void CHyprlock::run() { m_sLoopState.timerEvent = true; m_sLoopState.timerCV.notify_all(); g_pRenderer->asyncResourceGatherer->notify(); + g_pRenderer->asyncResourceGatherer->await(); m_vOutputs.clear(); + g_pEGL.reset(); wl_display_disconnect(m_sWaylandState.display); @@ -491,8 +678,6 @@ void CHyprlock::unlockSession() { ext_session_lock_v1_unlock_and_destroy(m_sLockState.lock); m_sLockState.lock = nullptr; - m_vOutputs.clear(); - g_pEGL.reset(); Debug::log(LOG, "Unlocked, exiting!"); m_bTerminate = true; @@ -619,3 +804,7 @@ std::string CHyprlock::spawnSync(const std::string& cmd) { } return result; } + +zwlr_screencopy_manager_v1* CHyprlock::getScreencopy() { + return m_sWaylandState.screencopy; +} \ No newline at end of file diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 0a2d590..c8d3db5 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -3,6 +3,8 @@ #include #include "ext-session-lock-v1-protocol.h" #include "fractional-scale-v1-protocol.h" +#include "linux-dmabuf-unstable-v1-protocol.h" +#include "wlr-screencopy-unstable-v1-protocol.h" #include "viewporter-protocol.h" #include "Output.hpp" #include "CursorShape.hpp" @@ -16,6 +18,14 @@ #include +#include +#include + +struct SDMABUFModifier { + uint32_t fourcc = 0; + uint64_t mod = 0; +}; + class CHyprlock { public: CHyprlock(const std::string& wlDisplay); @@ -49,6 +59,7 @@ class CHyprlock { wl_display* getDisplay(); wp_fractional_scale_manager_v1* getFractionalMgr(); wp_viewporter* getViewporter(); + zwlr_screencopy_manager_v1* getScreencopy(); wl_pointer* m_pPointer = nullptr; wl_keyboard* m_pKeeb = nullptr; @@ -65,6 +76,23 @@ class CHyprlock { std::chrono::system_clock::time_point m_tGraceEnds; Vector2D m_vLastEnterCoords = {}; + std::vector> m_vOutputs; + + struct { + void* linuxDmabuf = nullptr; + void* linuxDmabufFeedback = nullptr; + + gbm_bo* gbm = nullptr; + gbm_device* gbmDevice = nullptr; + + void* formatTable = nullptr; + size_t formatTableSize = 0; + bool deviceUsed = false; + + std::vector dmabufMods; + } dma; + gbm_device* createGBMDevice(drmDevice* dev); + private: struct { wl_display* display = nullptr; @@ -74,6 +102,7 @@ class CHyprlock { wl_compositor* compositor = nullptr; wp_fractional_scale_manager_v1* fractional = nullptr; wp_viewporter* viewporter = nullptr; + zwlr_screencopy_manager_v1* screencopy = nullptr; } m_sWaylandState; struct { @@ -98,9 +127,7 @@ class CHyprlock { bool timerEvent = false; } m_sLoopState; - std::vector> m_vOutputs; - - std::vector> m_vTimers; + std::vector> m_vTimers; }; inline std::unique_ptr g_pHyprlock; \ No newline at end of file diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp index 8ccf864..f4dccdb 100644 --- a/src/renderer/AsyncResourceGatherer.cpp +++ b/src/renderer/AsyncResourceGatherer.cpp @@ -15,6 +15,42 @@ CAsyncResourceGatherer::CAsyncResourceGatherer() { this->asyncLoopThread.detach(); }); initThread.detach(); + + // some things can't be done async :( + // gather background textures when needed + + const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); + + std::vector mons; + + for (auto& c : CWIDGETS) { + if (c.type != "background") + continue; + + if (std::string{std::any_cast(c.values.at("path"))} != "screenshot") + continue; + + // mamma mia + if (c.monitor.empty()) { + mons.clear(); + for (auto& m : g_pHyprlock->m_vOutputs) { + mons.push_back(m->stringPort); + } + break; + } else + mons.push_back(c.monitor); + } + + for (auto& mon : mons) { + const auto MON = std::find_if(g_pHyprlock->m_vOutputs.begin(), g_pHyprlock->m_vOutputs.end(), [mon](const auto& other) { return other->stringPort == mon; }); + + if (MON == g_pHyprlock->m_vOutputs.end()) + continue; + + const auto PMONITOR = MON->get(); + + dmas.emplace_back(std::make_unique(PMONITOR)); + } } SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) { @@ -36,6 +72,11 @@ SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) { } } + for (auto& dma : dmas) { + if (id == "dma:" + dma->name) + return dma->asset.ready ? &dma->asset : nullptr; + } + return nullptr; } @@ -228,6 +269,8 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() { asyncLoopState.busy = false; } + + dmas.clear(); } void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& request) { @@ -247,3 +290,8 @@ void CAsyncResourceGatherer::notify() { asyncLoopState.pending = true; asyncLoopState.loopGuard.notify_all(); } + +void CAsyncResourceGatherer::await() { + if (asyncLoopThread.joinable()) + asyncLoopThread.join(); +} diff --git a/src/renderer/AsyncResourceGatherer.hpp b/src/renderer/AsyncResourceGatherer.hpp index de7cf22..64a81e4 100644 --- a/src/renderer/AsyncResourceGatherer.hpp +++ b/src/renderer/AsyncResourceGatherer.hpp @@ -3,6 +3,7 @@ #include "Shader.hpp" #include "../helpers/Box.hpp" #include "../helpers/Color.hpp" +#include "DMAFrame.hpp" #include "Texture.hpp" #include #include @@ -10,10 +11,7 @@ #include #include #include - -struct SPreloadedAsset { - CTexture texture; -}; +#include "Shared.hpp" class CAsyncResourceGatherer { public: @@ -44,6 +42,7 @@ class CAsyncResourceGatherer { void requestAsyncAssetPreload(const SPreloadRequest& request); void unloadAsset(SPreloadedAsset* asset); void notify(); + void await(); private: std::thread initThread; @@ -77,6 +76,8 @@ class CAsyncResourceGatherer { Vector2D size; }; + std::vector> dmas; + std::vector preloadTargets; std::unordered_map assets; diff --git a/src/renderer/DMAFrame.cpp b/src/renderer/DMAFrame.cpp new file mode 100644 index 0000000..6e75f3e --- /dev/null +++ b/src/renderer/DMAFrame.cpp @@ -0,0 +1,203 @@ +#include "DMAFrame.hpp" +#include "wlr-screencopy-unstable-v1-protocol.h" +#include "../helpers/Log.hpp" +#include "../core/hyprlock.hpp" +#include "../core/Egl.hpp" + +#include +#include +#include +#include +#include + +static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; + +static void wlrOnBuffer(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { + const auto PDATA = (SScreencopyData*)data; + + Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)PDATA); + + PDATA->size = stride * height; + PDATA->stride = stride; +} + +static void wlrOnFlags(void* data, zwlr_screencopy_frame_v1* frame, uint32_t flags) { + ; +} + +static void wlrOnReady(void* data, zwlr_screencopy_frame_v1* frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + const auto PDATA = (SScreencopyData*)data; + + Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)PDATA); + + if (!PDATA->frame->onBufferReady()) { + Debug::log(ERR, "onBufferReady failed"); + return; + } + + zwlr_screencopy_frame_v1_destroy(frame); +} + +static void wlrOnFailed(void* data, zwlr_screencopy_frame_v1* frame) { + ; +} + +static void wlrOnDamage(void* data, zwlr_screencopy_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + ; +} + +static void wlrOnDmabuf(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) { + const auto PDATA = (SScreencopyData*)data; + + Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)PDATA); + + PDATA->w = width; + PDATA->h = height; + PDATA->fmt = format; +} + +static void wlrOnBufferDone(void* data, zwlr_screencopy_frame_v1* frame) { + const auto PDATA = (SScreencopyData*)data; + + Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)PDATA); + + if (!PDATA->frame->onBufferDone()) { + Debug::log(ERR, "onBufferDone failed"); + return; + } + + zwlr_screencopy_frame_v1_copy(frame, PDATA->frame->wlBuffer); + + Debug::log(TRACE, "[sc] wlr frame copied"); +} + +static const zwlr_screencopy_frame_v1_listener wlrFrameListener = { + .buffer = wlrOnBuffer, + .flags = wlrOnFlags, + .ready = wlrOnReady, + .failed = wlrOnFailed, + .damage = wlrOnDamage, + .linux_dmabuf = wlrOnDmabuf, + .buffer_done = wlrOnBufferDone, +}; + +CDMAFrame::CDMAFrame(COutput* output) { + + if (!glEGLImageTargetTexture2DOES) { + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + if (!glEGLImageTargetTexture2DOES) { + Debug::log(ERR, "No glEGLImageTargetTexture2DOES??"); + return; + } + } + + // firstly, plant a listener for the frame + frameCb = zwlr_screencopy_manager_v1_capture_output(g_pHyprlock->getScreencopy(), false, output->output); + + scdata.frame = this; + + zwlr_screencopy_frame_v1_add_listener(frameCb, &wlrFrameListener, &scdata); + + name = output->stringPort; +} + +CDMAFrame::~CDMAFrame() { + if (g_pEGL) + eglDestroyImage(g_pEGL->eglDisplay, image); +} + +bool CDMAFrame::onBufferDone() { + uint32_t flags = GBM_BO_USE_RENDERING; + + bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, flags); + + if (!bo) { + Debug::log(ERR, "Couldn't create a drm buffer"); + return false; + } + + planes = gbm_bo_get_plane_count(bo); + + zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params((zwp_linux_dmabuf_v1*)g_pHyprlock->dma.linuxDmabuf); + if (!params) { + Debug::log(ERR, "zwp_linux_dmabuf_v1_create_params failed"); + gbm_bo_destroy(bo); + return false; + } + + for (size_t plane = 0; plane < (size_t)planes; plane++) { + size[plane] = 0; + stride[plane] = gbm_bo_get_stride_for_plane(bo, plane); + offset[plane] = gbm_bo_get_offset(bo, plane); + uint64_t mod = gbm_bo_get_modifier(bo); + fd[plane] = gbm_bo_get_fd_for_plane(bo, plane); + + if (fd[plane] < 0) { + Debug::log(ERR, "gbm_bo_get_fd_for_plane failed"); + zwp_linux_buffer_params_v1_destroy(params); + gbm_bo_destroy(bo); + for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) { + close(fd[plane_tmp]); + } + return false; + } + + zwp_linux_buffer_params_v1_add(params, fd[plane], plane, offset[plane], stride[plane], mod >> 32, mod & 0xffffffff); + } + + wlBuffer = zwp_linux_buffer_params_v1_create_immed(params, scdata.w, scdata.h, scdata.fmt, 0); + zwp_linux_buffer_params_v1_destroy(params); + + if (!wlBuffer) { + Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed"); + gbm_bo_destroy(bo); + for (size_t plane = 0; plane < (size_t)planes; plane++) + close(fd[plane]); + + return false; + } + + return true; +} + +bool CDMAFrame::onBufferReady() { + static const int general_attribs = 3; + static const int plane_attribs = 5; + static const int entries_per_attrib = 2; + EGLAttrib attribs[(general_attribs + plane_attribs * 4) * entries_per_attrib + 1]; + int attr = 0; + attribs[attr++] = EGL_WIDTH; + attribs[attr++] = scdata.w; + attribs[attr++] = EGL_HEIGHT; + attribs[attr++] = scdata.h; + attribs[attr++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[attr++] = scdata.fmt; + attribs[attr++] = EGL_DMA_BUF_PLANE0_FD_EXT; + attribs[attr++] = fd[0]; + attribs[attr++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; + attribs[attr++] = offset[0]; + attribs[attr++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; + attribs[attr++] = stride[0]; + attribs[attr] = EGL_NONE; + + image = eglCreateImage(g_pEGL->eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); + + if (image == EGL_NO_IMAGE) { + Debug::log(ERR, "failed creating an egl image"); + return false; + } + + asset.texture.allocate(); + asset.texture.m_vSize = {scdata.w, scdata.h}; + glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); + glBindTexture(GL_TEXTURE_2D, 0); + + asset.ready = true; + + return true; +} \ No newline at end of file diff --git a/src/renderer/DMAFrame.hpp b/src/renderer/DMAFrame.hpp new file mode 100644 index 0000000..9763fbd --- /dev/null +++ b/src/renderer/DMAFrame.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "../core/Output.hpp" +#include +#include "Texture.hpp" +#include "Shared.hpp" + +struct zwlr_screencopy_frame_v1; + +class CDMAFrame; + +struct SScreencopyData { + int w = 0, h = 0; + uint32_t fmt; + size_t size; + size_t stride; + CDMAFrame* frame = nullptr; +}; + +class CDMAFrame { + public: + CDMAFrame(COutput* mon); + ~CDMAFrame(); + + bool onBufferDone(); + bool onBufferReady(); + + wl_buffer* wlBuffer = nullptr; + + std::string name; + + SPreloadedAsset asset; + + private: + gbm_bo* bo = nullptr; + + int planes = 0; + + int fd[4]; + uint32_t size[4], stride[4], offset[4]; + + zwlr_screencopy_frame_v1* frameCb = nullptr; + SScreencopyData scdata; + + EGLImage image = nullptr; +}; \ No newline at end of file diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index 11c8247..a317169 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -255,8 +255,14 @@ std::vector>* CRenderer::getOrCreateWidgetsFor(const CS // by type if (c.type == "background") { const std::string PATH = std::any_cast(c.values.at("path")); - widgets[surf].emplace_back( - std::make_unique(surf->size, PATH.empty() ? "" : std::string{"background:"} + PATH, std::any_cast(c.values.at("color")))); + + std::string resourceID = ""; + if (PATH == "screenshot") + resourceID = "dma:" + surf->output->stringPort; + else if (!PATH.empty()) + resourceID = "background:" + PATH; + + widgets[surf].emplace_back(std::make_unique(surf->size, resourceID, std::any_cast(c.values.at("color")))); } else if (c.type == "input-field") { widgets[surf].emplace_back(std::make_unique(surf->size, c.values)); } else if (c.type == "label") { diff --git a/src/renderer/Shared.hpp b/src/renderer/Shared.hpp new file mode 100644 index 0000000..b333f3f --- /dev/null +++ b/src/renderer/Shared.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "Texture.hpp" + +struct SPreloadedAsset { + CTexture texture; + bool ready = false; +}; \ No newline at end of file