aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt1
-rw-r--r--Makefile12
-rw-r--r--flake.lock19
-rw-r--r--flake.nix6
-rw-r--r--nix/default.nix5
-rw-r--r--protocols/meson.build10
-rw-r--r--src/Compositor.cpp12
-rw-r--r--src/Compositor.hpp2
-rw-r--r--src/events/Monitors.cpp2
-rw-r--r--src/events/Windows.cpp2
-rw-r--r--src/managers/ProtocolManager.cpp5
-rw-r--r--src/managers/ProtocolManager.hpp13
-rw-r--r--src/protocols/ToplevelExport.cpp382
-rw-r--r--src/protocols/ToplevelExport.hpp69
-rw-r--r--src/protocols/ToplevelExportWlrFuncs.hpp312
-rw-r--r--src/render/Renderer.cpp9
-rw-r--r--src/render/Renderer.hpp5
m---------subprojects/hyprland-protocols0
19 files changed, 862 insertions, 7 deletions
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
@@ -3,6 +3,22 @@
"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,
"narHash": "sha256-hxWGqlPecqEsE6nOHDV29KFBKePbY2Ipeac6lrChMKY=",
"owner": "hyprwm",
@@ -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<CHyprXWaylandManager>();
+ Debug::log(LOG, "Creating the ProtocolManager!");
+ g_pProtocolManager = std::make_unique<CProtocolManager>();
+
Debug::log(LOG, "Creating the EventManager!");
g_pEventManager = std::make_unique<CEventManager>();
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<CToplevelExportProtocolManager>();
+} \ 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<CToplevelExportProtocolManager> m_pToplevelExportProtocolManager;
+};
+
+inline std::unique_ptr<CProtocolManager> 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 <drm_fourcc.h>
+
+#include <algorithm>
+
+#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<SToplevelFrame*> 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 <list>
+#include <vector>
+
+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<SToplevelFrame> m_lFrames;
+ std::list<SToplevelClient> m_lClients;
+
+ wl_listener m_liDisplayDestroy;
+
+ std::vector<SToplevelFrame*> 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 <GLES2/gl2ext.h>
+
+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<std::unique_ptr<SLayerSurface>>&, 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<CHyprRenderer> g_pHyprRenderer;
diff --git a/subprojects/hyprland-protocols b/subprojects/hyprland-protocols
new file mode 160000
+Subproject 0dcff94fc10df2bbb66d3e1b5a1d6cfd3ada551