diff --git a/.gitmodules b/.gitmodules index 61cb4cef..ed443f60 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "wlroots"] path = subprojects/wlroots url = https://gitlab.freedesktop.org/wlroots/wlroots.git +[submodule "subprojects/hyprland-protocols"] + path = subprojects/hyprland-protocols + url = https://github.com/hyprwm/hyprland-protocols diff --git a/CMakeLists.txt b/CMakeLists.txt index cafd4205..b1b77ad0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ target_link_libraries(Hyprland pthread ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_SOURCE_DIR}/ext-workspace-unstable-v1-protocol.o + ${CMAKE_SOURCE_DIR}/hyprland-toplevel-export-v1-protocol.o ) IF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) diff --git a/Makefile b/Makefile index 1a193086..0c69b958 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,16 @@ wlr-output-power-management-unstable-v1-protocol.c: wlr-output-power-management-unstable-v1-protocol.o: wlr-output-power-management-unstable-v1-protocol.h +hyprland-toplevel-export-v1-protocol.h: + $(WAYLAND_SCANNER) server-header \ + subprojects/hyprland-protocols/protocols/hyprland-toplevel-export-v1.xml $@ + +hyprland-toplevel-export-v1-protocol.c: + $(WAYLAND_SCANNER) private-code \ + subprojects/hyprland-protocols/protocols/hyprland-toplevel-export-v1.xml $@ + +hyprland-toplevel-export-v1-protocol.o: hyprland-toplevel-export-v1-protocol.h + linux-dmabuf-unstable-v1-protocol.h: $(WAYLAND_SCANNER) server-header \ $(WAYLAND_PROTOCOLS)/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml $@ @@ -179,7 +189,7 @@ uninstall: rm -f ${PREFIX}/share/man/man1/Hyprland.1 rm -f ${PREFIX}/share/man/man1/hyprctl.1 -protocols: xdg-shell-protocol.o wlr-layer-shell-unstable-v1-protocol.o wlr-screencopy-unstable-v1-protocol.o idle-protocol.o ext-workspace-unstable-v1-protocol.o pointer-constraints-unstable-v1-protocol.o tablet-unstable-v2-protocol.o wlr-output-power-management-unstable-v1-protocol.o linux-dmabuf-unstable-v1-protocol.o +protocols: xdg-shell-protocol.o wlr-layer-shell-unstable-v1-protocol.o wlr-screencopy-unstable-v1-protocol.o idle-protocol.o ext-workspace-unstable-v1-protocol.o pointer-constraints-unstable-v1-protocol.o tablet-unstable-v2-protocol.o wlr-output-power-management-unstable-v1-protocol.o linux-dmabuf-unstable-v1-protocol.o hyprland-toplevel-export-v1-protocol.o fixwlr: sed -i -E 's/(soversion = 12)([^032]|$$)/soversion = 12032/g' subprojects/wlroots/meson.build diff --git a/flake.lock b/flake.lock index 572c561e..213725aa 100644 --- a/flake.lock +++ b/flake.lock @@ -1,6 +1,22 @@ { "nodes": { "hyprland-protocols": { + "flake": false, + "locked": { + "lastModified": 1670258048, + "narHash": "sha256-Lm2sXnDVZNE+taHqsqVibvPmSdu65VHvXI507KVX4lg=", + "owner": "hyprwm", + "repo": "hyprland-protocols", + "rev": "0dcff94fc10df2bbb66d3e1b5a1d6cfd3ada5515", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprland-protocols", + "type": "github" + } + }, + "hyprland-protocols_2": { "flake": false, "locked": { "lastModified": 1670185345, @@ -34,6 +50,7 @@ }, "root": { "inputs": { + "hyprland-protocols": "hyprland-protocols", "nixpkgs": "nixpkgs", "wlroots": "wlroots", "xdph": "xdph" @@ -59,7 +76,7 @@ }, "xdph": { "inputs": { - "hyprland-protocols": "hyprland-protocols", + "hyprland-protocols": "hyprland-protocols_2", "nixpkgs": [ "nixpkgs" ] diff --git a/flake.nix b/flake.nix index bb097d6a..666edf3f 100644 --- a/flake.nix +++ b/flake.nix @@ -12,6 +12,11 @@ url = "github:hyprwm/xdg-desktop-portal-hyprland"; inputs.nixpkgs.follows = "nixpkgs"; }; + + hyprland-protocols = { + url = "github:hyprwm/hyprland-protocols"; + flake = false; + }; }; outputs = inputs @ { @@ -64,6 +69,7 @@ stdenv = prev.gcc12Stdenv; version = "0.18.0beta" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); wlroots = wlroots-hyprland; + inherit (inputs) hyprland-protocols; }; hyprland-debug = hyprland.override {debug = true;}; hyprland-no-hidpi = hyprland.override {hidpiXWayland = false;}; diff --git a/nix/default.nix b/nix/default.nix index cf690857..58d63712 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -7,6 +7,7 @@ meson, ninja, git, + hyprland-protocols, libdrm, libinput, libxcb, @@ -92,6 +93,10 @@ in # Fix hardcoded paths to /usr installation postPatch = '' sed -i "s#/usr#$out#" src/render/OpenGL.cpp + + # for some reason rmdir doesn't work in a dirty tree + rmdir subprojects/hyprland-protocols || true + ln -s ${hyprland-protocols} subprojects/hyprland-protocols ''; passthru.providedSessions = ["hyprland"]; diff --git a/protocols/meson.build b/protocols/meson.build index 127406d9..d75acb3a 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -3,7 +3,14 @@ wayland_protos = dependency('wayland-protocols', fallback: 'wayland-protocols', default_options: ['tests=false'], ) + +hyprland_protos = dependency('hyprland-protocols', + version: '>=0.1', + fallback: 'hyprland-protocols', +) + wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') +hl_protocol_dir = hyprland_protos.get_variable('pkgdatadir') wayland_scanner_dep = dependency('wayland-scanner', native: true) wayland_scanner = find_program( @@ -19,7 +26,8 @@ protocols = [ ['ext-workspace-unstable-v1.xml'], ['pointer-constraints-unstable-v1.xml'], ['tablet-unstable-v2.xml'], - ['idle.xml'] + ['idle.xml'], + [hl_protocol_dir, 'protocols/hyprland-toplevel-export-v1.xml'] ] wl_protos_src = [] wl_protos_headers = [] diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 9c6df787..5cf43334 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -326,6 +326,9 @@ void CCompositor::startCompositor() { Debug::log(LOG, "Creating the XWaylandManager!"); g_pXWaylandManager = std::make_unique(); + Debug::log(LOG, "Creating the ProtocolManager!"); + g_pProtocolManager = std::make_unique(); + Debug::log(LOG, "Creating the EventManager!"); g_pEventManager = std::make_unique(); g_pEventManager->startThread(); @@ -909,6 +912,15 @@ CWindow* CCompositor::getWindowFromSurface(wlr_surface* pSurface) { return nullptr; } +CWindow* CCompositor::getWindowFromHandle(uint32_t handle) { + for (auto& w : m_vWindows) { + if ((uintptr_t)w.get() == (uintptr_t)handle) + return w.get(); + } + + return nullptr; +} + CWindow* CCompositor::getFullscreenWindowOnWorkspace(const int& ID) { for (auto& w : m_vWindows) { if (w->m_iWorkspaceID == ID && w->m_bIsFullscreen) diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 7bc47176..9d32692f 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -15,6 +15,7 @@ #include "managers/KeybindManager.hpp" #include "managers/AnimationManager.hpp" #include "managers/EventManager.hpp" +#include "managers/ProtocolManager.hpp" #include "debug/HyprDebugOverlay.hpp" #include "helpers/Monitor.hpp" #include "helpers/Workspace.hpp" @@ -126,6 +127,7 @@ public: CMonitor* getMonitorFromOutput(wlr_output*); CWindow* getWindowForPopup(wlr_xdg_popup*); CWindow* getWindowFromSurface(wlr_surface*); + CWindow* getWindowFromHandle(uint32_t); bool isWorkspaceVisible(const int&); CWorkspace* getWorkspaceByID(const int&); CWorkspace* getWorkspaceByName(const std::string&); diff --git a/src/events/Monitors.cpp b/src/events/Monitors.cpp index 9307c770..282cb4f9 100644 --- a/src/events/Monitors.cpp +++ b/src/events/Monitors.cpp @@ -274,6 +274,8 @@ void Events::listener_monitorFrame(void* owner, void* data) { g_pHyprOpenGL->end(); + g_pProtocolManager->m_pToplevelExportProtocolManager->onMonitorRender(PMONITOR); // dispatch any toplevel sharing + // calc frame damage pixman_region32_t frameDamage; pixman_region32_init(&frameDamage); diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index 6f524da2..9f44e23c 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -556,6 +556,8 @@ void Events::listener_unmapWindow(void* owner, void* data) { g_pEventManager->postEvent(SHyprIPCEvent{"closewindow", getFormat("%x", PWINDOW)}); + g_pProtocolManager->m_pToplevelExportProtocolManager->onWindowUnmap(PWINDOW); + if (!PWINDOW->m_bIsX11) { Debug::log(LOG, "Unregistered late callbacks XDG"); PWINDOW->hyprListener_commitWindow.removeCallback(); diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp new file mode 100644 index 00000000..34a6e679 --- /dev/null +++ b/src/managers/ProtocolManager.cpp @@ -0,0 +1,5 @@ +#include "ProtocolManager.hpp" + +CProtocolManager::CProtocolManager() { + m_pToplevelExportProtocolManager = std::make_unique(); +} \ No newline at end of file diff --git a/src/managers/ProtocolManager.hpp b/src/managers/ProtocolManager.hpp new file mode 100644 index 00000000..810bb547 --- /dev/null +++ b/src/managers/ProtocolManager.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "../defines.hpp" +#include "../protocols/ToplevelExport.hpp" + +class CProtocolManager { +public: + CProtocolManager(); + + std::unique_ptr m_pToplevelExportProtocolManager; +}; + +inline std::unique_ptr g_pProtocolManager; diff --git a/src/protocols/ToplevelExport.cpp b/src/protocols/ToplevelExport.cpp new file mode 100644 index 00000000..458171d0 --- /dev/null +++ b/src/protocols/ToplevelExport.cpp @@ -0,0 +1,382 @@ +#include "ToplevelExport.hpp" +#include "../Compositor.hpp" +#include + +#include + +#include "ToplevelExportWlrFuncs.hpp" + +#define TOPLEVEL_EXPORT_VERSION 1 + +static void bindManagerInt(wl_client* client, void* data, uint32_t version, uint32_t id) { + g_pProtocolManager->m_pToplevelExportProtocolManager->bindManager(client, data, version, id); +} + +static void handleDisplayDestroy(struct wl_listener* listener, void* data) { + g_pProtocolManager->m_pToplevelExportProtocolManager->displayDestroy(); +} + +void CToplevelExportProtocolManager::displayDestroy() { + wl_global_destroy(m_pGlobal); +} + +CToplevelExportProtocolManager::CToplevelExportProtocolManager() { + +#ifndef GLES32 + Debug::log(WARN, "Toplevel sharing is not supported on LEGACY_RENDERER!"); + return; +#endif + + m_pGlobal = wl_global_create(g_pCompositor->m_sWLDisplay, &hyprland_toplevel_export_manager_v1_interface, + TOPLEVEL_EXPORT_VERSION, this, bindManagerInt); + + if (!m_pGlobal) { + Debug::log(ERR, "ToplevelExportManager could not start! Sharing windows will not work!"); + return; + } + + m_liDisplayDestroy.notify = handleDisplayDestroy; + wl_display_add_destroy_listener(g_pCompositor->m_sWLDisplay, &m_liDisplayDestroy); + + Debug::log(LOG, "ToplevelExportManager started successfully!"); +} + +void handleCaptureToplevel(wl_client* client, wl_resource* resource, uint32_t frame, int32_t overlay_cursor, uint32_t handle) { + g_pProtocolManager->m_pToplevelExportProtocolManager->captureToplevel(client, resource, frame, overlay_cursor, handle); +} + +void handleDestroy(wl_client* client, wl_resource* resource) { + wl_resource_destroy(resource); +} + +void handleCopyFrame(wl_client* client, wl_resource* resource, wl_resource* buffer, int32_t ignore_damage) { + g_pProtocolManager->m_pToplevelExportProtocolManager->copyFrame(client, resource, buffer, ignore_damage); +} + +void handleDestroyFrame(wl_client* client, wl_resource* resource) { + wl_resource_destroy(resource); +} + +static const struct hyprland_toplevel_export_manager_v1_interface toplevelExportManagerImpl = { + .capture_toplevel = handleCaptureToplevel, + .destroy = handleDestroy +}; + +static const struct hyprland_toplevel_export_frame_v1_interface toplevelFrameImpl = { + .copy = handleCopyFrame, + .destroy = handleDestroyFrame +}; + +SToplevelClient* clientFromResource(wl_resource* resource) { + ASSERT(wl_resource_instance_of(resource, &hyprland_toplevel_export_manager_v1_interface, &toplevelExportManagerImpl)); + return (SToplevelClient*)wl_resource_get_user_data(resource); +} + +SToplevelFrame* frameFromResource(wl_resource* resource) { + ASSERT(wl_resource_instance_of(resource, &hyprland_toplevel_export_frame_v1_interface, &toplevelFrameImpl)); + return (SToplevelFrame*)wl_resource_get_user_data(resource); +} + +void CToplevelExportProtocolManager::removeClient(SToplevelClient* client, bool force) { + if (!force) { + if (!client || client->ref <= 0) + return; + + if (--client->ref != 0) + return; + } + + m_lClients.remove(*client); // TODO: this doesn't get cleaned up after sharing app exits??? +} + +void handleManagerResourceDestroy(wl_resource* resource) { + const auto PCLIENT = clientFromResource(resource); + + g_pProtocolManager->m_pToplevelExportProtocolManager->removeClient(PCLIENT); +} + +void CToplevelExportProtocolManager::bindManager(wl_client* client, void* data, uint32_t version, uint32_t id) { + const auto PCLIENT = &m_lClients.emplace_back(); + + PCLIENT->resource = wl_resource_create(client, &hyprland_toplevel_export_manager_v1_interface, + version, id); + + if (!PCLIENT->resource) { + Debug::log(ERR, "ToplevelExportManager could not bind! (out of memory?)"); + m_lClients.remove(*PCLIENT); + wl_client_post_no_memory(client); + return; + } + + PCLIENT->ref = 1; + + wl_resource_set_implementation(PCLIENT->resource, &toplevelExportManagerImpl, PCLIENT, handleManagerResourceDestroy); + + Debug::log(LOG, "ToplevelExportManager bound successfully!"); +} + +void handleFrameResourceDestroy(wl_resource* resource) { + const auto PFRAME = frameFromResource(resource); + + g_pProtocolManager->m_pToplevelExportProtocolManager->removeFrame(PFRAME); +} + +void CToplevelExportProtocolManager::removeFrame(SToplevelFrame* frame, bool force) { + if (!frame) + return; + + std::erase_if(m_vFramesAwaitingWrite, [&] (const auto& other) { return other == frame; }); + + wl_resource_set_user_data(frame->resource, nullptr); + wlr_buffer_unlock(frame->buffer); + removeClient(frame->client, force); + m_lFrames.remove(*frame); +} + +void CToplevelExportProtocolManager::captureToplevel(wl_client* client, wl_resource* resource, uint32_t frame, int32_t overlay_cursor, uint32_t handle) { + const auto PCLIENT = clientFromResource(resource); + + const auto PWINDOW = g_pCompositor->getWindowFromHandle(handle); + + // create a frame + const auto PFRAME = &m_lFrames.emplace_back(); + PFRAME->overlayCursor = !!overlay_cursor; + PFRAME->resource = wl_resource_create(client, &hyprland_toplevel_export_frame_v1_interface, wl_resource_get_version(resource), frame); + PFRAME->pWindow = PWINDOW; + + if (!PWINDOW) { + Debug::log(ERR, "Client requested sharing of window handle %x which does not exist!", handle); + hyprland_toplevel_export_frame_v1_send_failed(PFRAME->resource); + removeFrame(PFRAME); + return; + } + + if (!PWINDOW->m_bIsMapped || PWINDOW->isHidden()) { + Debug::log(ERR, "Client requested sharing of window handle %x which is not shareable!", handle); + hyprland_toplevel_export_frame_v1_send_failed(PFRAME->resource); + removeFrame(PFRAME); + return; + } + + if (!PFRAME->resource) { + Debug::log(ERR, "Couldn't alloc frame for sharing! (no memory)"); + m_lFrames.remove(*PFRAME); + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(PFRAME->resource, &toplevelFrameImpl, PFRAME, handleFrameResourceDestroy); + + PFRAME->client = PCLIENT; + PCLIENT->ref++; + + const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); + + PFRAME->shmFormat = wlr_output_preferred_read_format(PMONITOR->output); + if (PFRAME->shmFormat == DRM_FORMAT_INVALID) { + Debug::log(ERR, "No format supported by renderer in capture toplevel"); + hyprland_toplevel_export_frame_v1_send_failed(resource); + removeFrame(PFRAME); + return; + } + + const auto PSHMINFO = drm_get_pixel_format_info(PFRAME->shmFormat); + if (!PSHMINFO) { + Debug::log(ERR, "No pixel format supported by renderer in capture toplevel"); + hyprland_toplevel_export_frame_v1_send_failed(resource); + removeFrame(PFRAME); + return; + } + + if (PMONITOR->output->allocator && (PMONITOR->output->allocator->buffer_caps & WLR_BUFFER_CAP_DMABUF)) { + PFRAME->dmabufFormat = PMONITOR->output->render_format; + } else { + PFRAME->dmabufFormat = DRM_FORMAT_INVALID; + } + + PFRAME->box = {0, 0, (int)PWINDOW->m_vRealSize.vec().x, (int)PWINDOW->m_vRealSize.vec().y}; + int ow, oh; + wlr_output_effective_resolution(PMONITOR->output, &ow, &oh); + wlr_box_transform(&PFRAME->box, &PFRAME->box, PMONITOR->transform, ow, oh); + + PFRAME->shmStride = (PSHMINFO->bpp / 8) * PFRAME->box.width; + + hyprland_toplevel_export_frame_v1_send_buffer(PFRAME->resource, convert_drm_format_to_wl_shm(PFRAME->shmFormat), PFRAME->box.width, PFRAME->box.height, PFRAME->shmStride); +} + +void CToplevelExportProtocolManager::copyFrame(wl_client* client, wl_resource* resource, wl_resource* buffer, int32_t ignore_damage) { + const auto PFRAME = frameFromResource(resource); + + if (!PFRAME) { + Debug::log(ERR, "No frame in copyFrame??"); + return; + } + + if (!PFRAME->pWindow->m_bIsMapped || PFRAME->pWindow->isHidden()) { + Debug::log(ERR, "Client requested sharing of window handle %x which is not shareable (2)!", PFRAME->pWindow); + hyprland_toplevel_export_frame_v1_send_failed(PFRAME->resource); + return; + } + + const auto PBUFFER = wlr_buffer_from_resource(buffer); + if (!PBUFFER) { + wl_resource_post_error(PFRAME->resource, HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); + return; + } + + if (PBUFFER->width != PFRAME->box.width || PBUFFER->height != PFRAME->box.height) { + wl_resource_post_error(PFRAME->resource, HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer dimensions"); + return; + } + + if (PFRAME->buffer) { + wl_resource_post_error(PFRAME->resource, HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); + return; + } + + wlr_dmabuf_attributes dmabufAttrs; + void* wlrBufferAccessData; + uint32_t wlrBufferAccessFormat; + size_t wlrBufferAccessStride; + if (wlr_buffer_get_dmabuf(PBUFFER, &dmabufAttrs)) { + PFRAME->bufferCap = WLR_BUFFER_CAP_DMABUF; + + if (dmabufAttrs.format != PFRAME->dmabufFormat) { + wl_resource_post_error(PFRAME->resource, HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); + return; + } + } else if (wlr_buffer_begin_data_ptr_access(PBUFFER, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &wlrBufferAccessData, &wlrBufferAccessFormat, &wlrBufferAccessStride)) { + wlr_buffer_end_data_ptr_access(PBUFFER); + + if (wlrBufferAccessFormat != PFRAME->shmFormat) { + wl_resource_post_error(PFRAME->resource, HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); + return; + } else if ((int)wlrBufferAccessStride != PFRAME->shmStride) { + wl_resource_post_error(PFRAME->resource, HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer stride"); + return; + } + } else { + wl_resource_post_error(PFRAME->resource, HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer type"); + return; + } + + PFRAME->buffer = PBUFFER; + + m_vFramesAwaitingWrite.emplace_back(PFRAME); +} + +void CToplevelExportProtocolManager::onMonitorRender(CMonitor* pMonitor) { + + if (m_vFramesAwaitingWrite.empty()) + return; // nothing to share + + std::vector framesToRemove; + + // share frame if correct output + for (auto& f : m_vFramesAwaitingWrite) { + if (!f->pWindow) { + framesToRemove.push_back(f); + continue; + } + + wlr_box geometry = { f->pWindow->m_vRealPosition.vec().x, f->pWindow->m_vRealPosition.vec().y, + f->pWindow->m_vRealSize.vec().x, f->pWindow->m_vRealSize.vec().y }; + + if (!wlr_output_layout_intersects(g_pCompositor->m_sWLROutputLayout, pMonitor->output, &geometry)) + continue; + + shareFrame(f); + + framesToRemove.push_back(f); + } + + for (auto& f : framesToRemove) { + removeFrame(f); + } +} + +void CToplevelExportProtocolManager::shareFrame(SToplevelFrame* frame) { + if (!frame->buffer) { + return; + } + + // TODO: damage + + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + uint32_t flags = 0; + if (frame->bufferCap == WLR_BUFFER_CAP_DMABUF) { + if (!copyFrameDmabuf(frame)) { + hyprland_toplevel_export_frame_v1_send_failed(frame->resource); + return; + } + } else { + if (!copyFrameShm(frame, &now)) { + hyprland_toplevel_export_frame_v1_send_failed(frame->resource); + return; + } + } + + hyprland_toplevel_export_frame_v1_send_flags(frame->resource, flags); + // todo: send damage + uint32_t tvSecHi = (sizeof(now.tv_sec) > 4) ? now.tv_sec >> 32 : 0; + uint32_t tvSecLo = now.tv_sec & 0xFFFFFFFF; + hyprland_toplevel_export_frame_v1_send_ready(frame->resource, tvSecHi, tvSecLo, now.tv_nsec); +} + +bool CToplevelExportProtocolManager::copyFrameShm(SToplevelFrame* frame, timespec* now) { + void* data; + uint32_t format; + size_t stride; + if (!wlr_buffer_begin_data_ptr_access(frame->buffer, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) + return false; + + // render the client + const auto PMONITOR = g_pCompositor->getMonitorFromID(frame->pWindow->m_iMonitorID); + pixman_region32_t fakeDamage; + pixman_region32_init_rect(&fakeDamage, 0, 0, PMONITOR->vecPixelSize.x * 10, PMONITOR->vecPixelSize.y * 10); + + g_pHyprOpenGL->begin(PMONITOR, &fakeDamage, true); + g_pHyprOpenGL->clear(CColor(0, 0, 0, 255)); + + // render client at 0,0 + g_pHyprRenderer->renderWindow(frame->pWindow, PMONITOR, now, false, RENDER_PASS_ALL, true, true); + + // copy pixels + const auto PFORMAT = get_gles2_format_from_drm(format); + if (!PFORMAT) { + Debug::log(ERR, "Cannot read pixels, unsupported format %x", PFORMAT); + g_pHyprOpenGL->end(); + pixman_region32_fini(&fakeDamage); + wlr_buffer_end_data_ptr_access(frame->buffer); + return false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, g_pHyprOpenGL->m_RenderData.pCurrentMonData->primaryFB.m_iFb); + + glFinish(); // flush + + glReadPixels(0, 0, frame->box.width, frame->box.height, PFORMAT->gl_format, PFORMAT->gl_type, data); + + g_pHyprOpenGL->end(); + + pixman_region32_fini(&fakeDamage); + + wlr_buffer_end_data_ptr_access(frame->buffer); + + return true; +} + +bool CToplevelExportProtocolManager::copyFrameDmabuf(SToplevelFrame* frame) { + // todo + Debug::log(ERR, "DMABUF copying not impl'd!"); + return false; +} + +void CToplevelExportProtocolManager::onWindowUnmap(CWindow* pWindow) { + for (auto& f : m_lFrames) { + if (f.pWindow == pWindow) + f.pWindow = nullptr; + } +} \ No newline at end of file diff --git a/src/protocols/ToplevelExport.hpp b/src/protocols/ToplevelExport.hpp new file mode 100644 index 00000000..bdf5cfd6 --- /dev/null +++ b/src/protocols/ToplevelExport.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "../defines.hpp" +#include "hyprland-toplevel-export-v1-protocol.h" + +#include +#include + +class CMonitor; +class CWindow; + +struct SToplevelClient { + int ref = 0; + wl_resource* resource = nullptr; + + bool operator==(const SToplevelClient& other) { + return resource == other.resource; + } +}; + +struct SToplevelFrame { + wl_resource* resource = nullptr; + SToplevelClient* client = nullptr; + + uint32_t shmFormat = 0; + uint32_t dmabufFormat = 0; + wlr_box box = { 0 }; + int shmStride = 0; + + bool overlayCursor = false; + + wlr_buffer_cap bufferCap = WLR_BUFFER_CAP_SHM; + + wlr_buffer* buffer = nullptr; + + CWindow* pWindow = nullptr; + + bool operator==(const SToplevelFrame& other) { + return resource == other.resource && client == other.client; + } +}; + +class CToplevelExportProtocolManager { +public: + CToplevelExportProtocolManager(); + + void bindManager(wl_client* client, void* data, uint32_t version, uint32_t id); + void captureToplevel(wl_client* client, wl_resource* resource, uint32_t frame, int32_t overlay_cursor, uint32_t handle); + void removeClient(SToplevelClient* client, bool force = false); + void removeFrame(SToplevelFrame* frame, bool force = false); + void copyFrame(wl_client* client, wl_resource* resource, wl_resource* buffer, int32_t ignore_damage); + + void onMonitorRender(CMonitor* pMonitor); + void displayDestroy(); + void onWindowUnmap(CWindow* pWindow); + +private: + wl_global* m_pGlobal = nullptr; + std::list m_lFrames; + std::list m_lClients; + + wl_listener m_liDisplayDestroy; + + std::vector m_vFramesAwaitingWrite; + + void shareFrame(SToplevelFrame* frame); + bool copyFrameDmabuf(SToplevelFrame* frame); + bool copyFrameShm(SToplevelFrame* frame, timespec* now); +}; \ No newline at end of file diff --git a/src/protocols/ToplevelExportWlrFuncs.hpp b/src/protocols/ToplevelExportWlrFuncs.hpp new file mode 100644 index 00000000..1423f617 --- /dev/null +++ b/src/protocols/ToplevelExportWlrFuncs.hpp @@ -0,0 +1,312 @@ +#include + +struct wlr_pixel_format_info { + uint32_t drm_format; + + /* Equivalent of the format if it has an alpha channel, + * DRM_FORMAT_INVALID (0) if NA + */ + uint32_t opaque_substitute; + + /* Bits per pixels */ + uint32_t bpp; + + /* True if the format has an alpha channel */ + bool has_alpha; +}; + +static const struct wlr_pixel_format_info pixel_format_info[] = { + { + .drm_format = DRM_FORMAT_XRGB8888, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 32, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ARGB8888, + .opaque_substitute = DRM_FORMAT_XRGB8888, + .bpp = 32, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR8888, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 32, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR8888, + .opaque_substitute = DRM_FORMAT_XBGR8888, + .bpp = 32, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGBX8888, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 32, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_RGBA8888, + .opaque_substitute = DRM_FORMAT_RGBX8888, + .bpp = 32, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_BGRX8888, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 32, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_BGRA8888, + .opaque_substitute = DRM_FORMAT_BGRX8888, + .bpp = 32, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_BGR888, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 24, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_RGBX4444, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 16, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_RGBA4444, + .opaque_substitute = DRM_FORMAT_RGBX4444, + .bpp = 16, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGBX5551, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 16, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_RGBA5551, + .opaque_substitute = DRM_FORMAT_RGBX5551, + .bpp = 16, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGB565, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 16, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_BGR565, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 16, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_XRGB2101010, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 32, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ARGB2101010, + .opaque_substitute = DRM_FORMAT_XRGB2101010, + .bpp = 32, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR2101010, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 32, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR2101010, + .opaque_substitute = DRM_FORMAT_XBGR2101010, + .bpp = 32, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616F, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 64, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616F, + .opaque_substitute = DRM_FORMAT_XBGR16161616F, + .bpp = 64, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616, + .opaque_substitute = DRM_FORMAT_INVALID, + .bpp = 64, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616, + .opaque_substitute = DRM_FORMAT_XBGR16161616, + .bpp = 64, + .has_alpha = true, + }, +}; + +static const size_t pixel_format_info_size = + sizeof(pixel_format_info) / sizeof(pixel_format_info[0]); + +const struct wlr_pixel_format_info* drm_get_pixel_format_info(uint32_t fmt) { + for (size_t i = 0; i < pixel_format_info_size; ++i) { + if (pixel_format_info[i].drm_format == fmt) { + return &pixel_format_info[i]; + } + } + + return NULL; +} + +uint32_t convert_wl_shm_format_to_drm(enum wl_shm_format fmt) { + switch (fmt) { + case WL_SHM_FORMAT_XRGB8888: + return DRM_FORMAT_XRGB8888; + case WL_SHM_FORMAT_ARGB8888: + return DRM_FORMAT_ARGB8888; + default: + return (uint32_t)fmt; + } +} + +enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt) { + switch (fmt) { + case DRM_FORMAT_XRGB8888: + return WL_SHM_FORMAT_XRGB8888; + case DRM_FORMAT_ARGB8888: + return WL_SHM_FORMAT_ARGB8888; + default: + return (enum wl_shm_format)fmt; + } +} + +struct wlr_gles2_pixel_format { + uint32_t drm_format; + // optional field, if empty then internalformat = format + GLint gl_internalformat; + GLint gl_format, gl_type; + bool has_alpha; +}; + +static const struct wlr_gles2_pixel_format formats[] = { + { + .drm_format = DRM_FORMAT_ARGB8888, + .gl_format = GL_BGRA_EXT, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XRGB8888, + .gl_format = GL_BGRA_EXT, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_XBGR8888, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR8888, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_BGR888, + .gl_format = GL_RGB, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = false, + }, +#if WLR_LITTLE_ENDIAN + { + .drm_format = DRM_FORMAT_RGBX4444, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_RGBA4444, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGBX5551, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_RGBA5551, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGB565, + .gl_format = GL_RGB, + .gl_type = GL_UNSIGNED_SHORT_5_6_5, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_XBGR2101010, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR2101010, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616F, + .gl_format = GL_RGBA, + .gl_type = GL_HALF_FLOAT_OES, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616F, + .gl_format = GL_RGBA, + .gl_type = GL_HALF_FLOAT_OES, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616, + .gl_internalformat = GL_RGBA16_EXT, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616, + .gl_internalformat = GL_RGBA16_EXT, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT, + .has_alpha = true, + }, +#endif +}; + +const struct wlr_gles2_pixel_format* get_gles2_format_from_drm(uint32_t fmt) { + for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { + if (formats[i].drm_format == fmt) { + return &formats[i]; + } + } + return NULL; +} diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 5f527830..5dd70acf 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -215,7 +215,7 @@ void CHyprRenderer::renderWorkspaceWithFullscreenWindow(CMonitor* pMonitor, CWor g_pHyprError->draw(); } -void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* time, bool decorate, eRenderPassMode mode, bool ignorePosition) { +void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* time, bool decorate, eRenderPassMode mode, bool ignorePosition, bool ignoreAllGeometry) { if (pWindow->isHidden()) return; @@ -235,6 +235,9 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* renderdata.y = pMonitor->vecPosition.y; } + if (ignoreAllGeometry) + decorate = false; + renderdata.surface = g_pXWaylandManager->getWindowSurface(pWindow); renderdata.w = std::max(pWindow->m_vRealSize.vec().x, 5.0); // clamp the size to min 5, renderdata.h = std::max(pWindow->m_vRealSize.vec().y, 5.0); // otherwise we'll have issues later with invalid boxes @@ -242,8 +245,8 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* renderdata.fadeAlpha = pWindow->m_fAlpha.fl() * (pWindow->m_bPinned ? 1.f : (PWORKSPACE->m_fAlpha.fl() / 255.f)); renderdata.alpha = pWindow->m_fActiveInactiveAlpha.fl(); renderdata.decorate = decorate && !pWindow->m_bX11DoesntWantBorders && (pWindow->m_bIsFloating ? *PNOFLOATINGBORDERS == 0 : true) && (!pWindow->m_bIsFullscreen || PWORKSPACE->m_efFullscreenMode != FULLSCREEN_FULL); - renderdata.rounding = pWindow->m_sAdditionalConfigData.rounding; - renderdata.blur = true; // if it shouldn't, it will be ignored later + renderdata.rounding = ignoreAllGeometry ? 0 : pWindow->m_sAdditionalConfigData.rounding; + renderdata.blur = !ignoreAllGeometry; // if it shouldn't, it will be ignored later renderdata.pWindow = pWindow; // apply window special data diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index 8bd65418..068f5588 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -23,6 +23,8 @@ enum eRenderPassMode { RENDER_PASS_POPUP }; +class CToplevelExportProtocolManager; + class CHyprRenderer { public: @@ -54,7 +56,7 @@ public: private: void arrangeLayerArray(CMonitor*, const std::vector>&, bool, wlr_box*); void renderWorkspaceWithFullscreenWindow(CMonitor*, CWorkspace*, timespec*); - void renderWindow(CWindow*, CMonitor*, timespec*, bool, eRenderPassMode, bool ignorePosition = false); + void renderWindow(CWindow*, CMonitor*, timespec*, bool, eRenderPassMode, bool ignorePosition = false, bool ignoreAllGeometry = false); void renderLayer(SLayerSurface*, CMonitor*, timespec*); void renderDragIcon(CMonitor*, timespec*); void renderIMEPopup(SIMEPopup*, CMonitor*, timespec*); @@ -63,6 +65,7 @@ private: friend class CHyprOpenGLImpl; + friend class CToplevelExportProtocolManager; }; inline std::unique_ptr g_pHyprRenderer; diff --git a/subprojects/hyprland-protocols b/subprojects/hyprland-protocols new file mode 160000 index 00000000..0dcff94f --- /dev/null +++ b/subprojects/hyprland-protocols @@ -0,0 +1 @@ +Subproject commit 0dcff94fc10df2bbb66d3e1b5a1d6cfd3ada5515