diff --git a/.gitignore b/.gitignore index 4cc70f8..e2d84e4 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ *.pdb # Kernel Module Compile Results +*.mod* *.cmd .tmp_versions/ modules.order @@ -56,9 +57,8 @@ build-*/ .cache .vscode/ -.idea hyprland-share-picker/build/ protocols/*.c -protocols/*.h +protocols/*.h \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c125069..d1ed1be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,9 @@ include_directories( ) set(CMAKE_CXX_STANDARD 23) -add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing -Wno-pointer-arith -fpermissive) +add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value + -Wno-missing-field-initializers -Wno-narrowing -Wno-pointer-arith + -fpermissive -Wno-address-of-temporary) message(STATUS "Checking deps...") add_subdirectory(subprojects/sdbus-cpp) @@ -34,7 +36,7 @@ add_subdirectory(hyprland-share-picker) find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) -pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols libpipewire-0.3 libspa-0.2 libdrm gbm) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols libpipewire-0.3 libspa-0.2 libdrm gbm hyprlang>=0.2.0) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") add_executable(xdg-desktop-portal-hyprland ${SRCFILES}) diff --git a/README.md b/README.md index dbe7b91..d147e01 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,13 @@ -# xdg-desktop-portal-hyprland With GTK +# xdg-desktop-portal-hyprland An [XDG Desktop Portal](https://github.com/flatpak/xdg-desktop-portal) backend for Hyprland. ## Installing ```sh -git clone -b theoparis/fix-build --recursive https://github.com/dragontos/xdg-desktop-portal-hyprland +git clone --recursive https://github.com/hyprwm/xdg-desktop-portal-hyprland cd xdg-desktop-portal-hyprland/ make all sudo make install ``` -## Make all -```sh -make all -sudo make install -``` -## Make hyprland-share-picker -``` -meson build -ninja -C build -``` ## Running, FAQs, etc. See [the Hyprland wiki](https://wiki.hyprland.org/Useful-Utilities/Hyprland-desktop-portal/) diff --git a/VERSION b/VERSION index 3c43790..f0bb29e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.6 +1.3.0 diff --git a/flake.lock b/flake.lock index 8e3f02a..8950839 100644 --- a/flake.lock +++ b/flake.lock @@ -23,13 +23,47 @@ "type": "github" } }, + "hyprlang": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1704287638, + "narHash": "sha256-TuRXJGwtK440AXQNl5eiqmQqY4LZ/9+z/R7xC0ie3iA=", + "owner": "hyprwm", + "repo": "hyprlang", + "rev": "6624f2bb66d4d27975766e81f77174adbe58ec97", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprlang", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1694183432, - "narHash": "sha256-YyPGNapgZNNj51ylQMw9lAgvxtM2ai1HZVUu3GS8Fng=", + "lastModified": 1702645756, + "narHash": "sha256-qKI6OR3TYJYQB3Q8mAZ+DG4o/BR9ptcv9UnRV2hzljc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "40c3c94c241286dd2243ea34d3aef8a488f9e4d0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1703961334, + "narHash": "sha256-M1mV/Cq+pgjk0rt6VxoyyD+O8cOUiai8t9Q6Yyq4noY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "db9208ab987cdeeedf78ad9b4cf3c55f5ebd269b", + "rev": "b0d36bd0a420ecee3bc916c91886caca87c894e9", "type": "github" }, "original": { @@ -42,7 +76,8 @@ "root": { "inputs": { "hyprland-protocols": "hyprland-protocols", - "nixpkgs": "nixpkgs", + "hyprlang": "hyprlang", + "nixpkgs": "nixpkgs_2", "systems": "systems" } }, diff --git a/flake.nix b/flake.nix index a5e4c4e..e489a02 100644 --- a/flake.nix +++ b/flake.nix @@ -12,6 +12,8 @@ inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; + + hyprlang.url = "github:hyprwm/hyprlang"; }; outputs = { diff --git a/go.mod b/go.mod deleted file mode 100644 index f253f98..0000000 --- a/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/DRAGONTOS/xdg-desktop-portal-hyprland - -go 1.21.4 - -require ( - github.com/diamondburned/gotk4/pkg v0.0.5 - github.com/edjubert/hyprland-ipc-go v0.0.26 - github.com/edjubert/hyprland-share-picker-gtk v0.0.1 -) - -require ( - go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 5b0a756..0000000 --- a/go.sum +++ /dev/null @@ -1,12 +0,0 @@ -github.com/diamondburned/gotk4/pkg v0.0.5 h1:1NOpwNttT63zJdRItwf79ZyX6Bw2bkv8+YjfZCUevcc= -github.com/diamondburned/gotk4/pkg v0.0.5/go.mod h1:Jr5xzUuGyLsoMtE9LzE/Lay1gD0ONiFWAmQ0+Z6KLeA= -github.com/edjubert/hyprland-ipc-go v0.0.26 h1:+gG9YCJ2bLouADW4HiFZWSjQypCUSGozAcds7lk0tmc= -github.com/edjubert/hyprland-ipc-go v0.0.26/go.mod h1:rUOpZlaFG+Aoa8lYMcVmkgD2m92H/Ojfn+buCNgmZHU= -github.com/edjubert/hyprland-share-picker-gtk v0.0.1 h1:2DBAG2x1vMvqWOfDtmqiILAzcxrzIICh5hrHf/jq5hM= -github.com/edjubert/hyprland-share-picker-gtk v0.0.1/go.mod h1:ClrE1cTwyG1fV8zyqvW2AcgC/3IWBd8E8Zki49DIceo= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/nix/default.nix b/nix/default.nix index a4da0ca..f960f11 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,6 +5,7 @@ meson, ninja, pkg-config, + hyprlang, libdrm, mesa, pipewire, @@ -48,6 +49,7 @@ stdenv.mkDerivation { libdrm mesa pipewire + hyprlang qtbase qttools qtwayland diff --git a/nix/overlays.nix b/nix/overlays.nix index 66524ce..550045c 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -25,6 +25,7 @@ in { inherit (final) hyprland-protocols; inherit (final.qt6) qtbase qttools wrapQtAppsHook qtwayland; inherit version; + inherit (inputs.hyprlang.packages.${prev.system}) hyprlang; }; }; } diff --git a/src/core/PortalManager.cpp b/src/core/PortalManager.cpp index 1dd872f..40d4a14 100644 --- a/src/core/PortalManager.cpp +++ b/src/core/PortalManager.cpp @@ -198,6 +198,25 @@ inline const zwp_linux_dmabuf_feedback_v1_listener dmabufFeedbackListener = { // +CPortalManager::CPortalManager() { + const auto XDG_CONFIG_HOME = getenv("XDG_CONFIG_HOME"); + const auto HOME = getenv("HOME"); + + if (!HOME && !XDG_CONFIG_HOME) { + Debug::log(CRIT, "Cannot proceed: neither $HOME nor $XDG_CONFIG_HOME is present in env"); + throw "$HOME and $XDG_CONFIG_HOME both missing from env"; + } + + std::string path = XDG_CONFIG_HOME ? std::string{XDG_CONFIG_HOME} + "/hypr/xdph.conf" : std::string{HOME} + "/.config/hypr/xdph.conf"; + + m_sConfig.config = std::make_unique(path.c_str(), Hyprlang::SConfigOptions{.allowMissingConfig = true}); + + m_sConfig.config->addConfigValue("general:toplevel_dynamic_bind", {0L}); + + m_sConfig.config->commence(); + m_sConfig.config->parse(); +} + void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { const std::string INTERFACE = interface; @@ -234,9 +253,13 @@ void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t else if (INTERFACE == wl_shm_interface.name) m_sWaylandConnection.shm = (wl_shm*)wl_registry_bind(registry, name, &wl_shm_interface, version); - else if (INTERFACE == zwlr_foreign_toplevel_manager_v1_interface.name) - m_sHelpers.toplevel = - std::make_unique((zwlr_foreign_toplevel_manager_v1*)wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, version)); + else if (INTERFACE == zwlr_foreign_toplevel_manager_v1_interface.name) { + m_sHelpers.toplevel = std::make_unique(registry, name, version); + + // remove when another fix is found for https://github.com/hyprwm/xdg-desktop-portal-hyprland/issues/147 + if (!std::any_cast(m_sConfig.config->getConfigValue("general:toplevel_dynamic_bind"))) + m_sHelpers.toplevel->activate(); + } } void CPortalManager::onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name) { @@ -331,7 +354,7 @@ void CPortalManager::startEventLoop() { std::thread pollThr([this, &pollfds]() { while (1) { - int ret = poll(pollfds, 3, 5 /* 5 seconds, reasonable. It's because we might need to terminate */); + int ret = poll(pollfds, 3, 5000 /* 5 seconds, reasonable. It's because we might need to terminate */); if (ret < 0) { Debug::log(CRIT, "[core] Polling fds failed with {}", strerror(errno)); g_pPortalManager->terminate(); diff --git a/src/core/PortalManager.hpp b/src/core/PortalManager.hpp index 9b341e1..a701fdb 100644 --- a/src/core/PortalManager.hpp +++ b/src/core/PortalManager.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../portals/Screencopy.hpp" #include "../portals/Screenshot.hpp" @@ -30,6 +31,8 @@ struct SDMABUFModifier { class CPortalManager { public: + CPortalManager(); + void init(); void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version); @@ -67,6 +70,10 @@ class CPortalManager { } dma; } m_sWaylandConnection; + struct { + std::unique_ptr config; + } m_sConfig; + std::vector m_vDMABUFMods; void addTimer(const CTimer& timer); diff --git a/src/meson.build b/src/meson.build index c3d3735..f816508 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,6 +5,7 @@ executable('xdg-desktop-portal-hyprland', [src, wl_proto_files], dependencies: [ dependency('gbm'), + dependency('hyprlang'), dependency('libdrm'), dependency('libpipewire-0.3'), dependency('sdbus-c++'), diff --git a/src/portals/Screencopy.cpp b/src/portals/Screencopy.cpp index afd0012..f0f7a77 100644 --- a/src/portals/Screencopy.cpp +++ b/src/portals/Screencopy.cpp @@ -46,7 +46,8 @@ static void wlrOnReady(void* data, zwlr_screencopy_frame_v1* frame, uint32_t tv_ g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(PSESSION); - g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION); + if (g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(PSESSION)) + g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION); zwlr_screencopy_frame_v1_destroy(frame); PSESSION->sharingData.frameCallback = nullptr; @@ -284,6 +285,8 @@ static const hyprland_toplevel_export_frame_v1_listener hyprlandFrameListener = void CScreencopyPortal::onCreateSession(sdbus::MethodCall& call) { sdbus::ObjectPath requestHandle, sessionHandle; + g_pPortalManager->m_sHelpers.toplevel->activate(); + call >> requestHandle; call >> sessionHandle; @@ -299,13 +302,16 @@ void CScreencopyPortal::onCreateSession(sdbus::MethodCall& call) { // create objects PSESSION->session = createDBusSession(sessionHandle); - PSESSION->session->onDestroy = [PSESSION, this]() { + PSESSION->session->onDestroy = [PSESSION, this]() { if (PSESSION->sharingData.active) { m_pPipewire->destroyStream(PSESSION); Debug::log(LOG, "[screencopy] Stream destroyed"); } PSESSION->session.release(); Debug::log(LOG, "[screencopy] Session destroyed"); + + // deactivate toplevel so it doesn't listen and waste battery + g_pPortalManager->m_sHelpers.toplevel->deactivate(); }; PSESSION->request = createDBusRequest(requestHandle); PSESSION->request->onDestroy = [PSESSION]() { PSESSION->request.release(); }; @@ -1084,7 +1090,7 @@ uint32_t CPipewireConnection::buildFormatsFor(spa_pod_builder* b[2], const spa_p paramCount = 2; params[0] = build_format(b[0], pwFromDrmFourcc(stream->pSession->sharingData.frameInfoDMA.fmt), stream->pSession->sharingData.frameInfoDMA.w, - stream->pSession->sharingData.frameInfoDMA.h, stream->pSession->sharingData.framerate, modifiers, modCount); + stream->pSession->sharingData.frameInfoDMA.h, stream->pSession->sharingData.framerate, modifiers, modCount); assert(params[0] != NULL); params[1] = build_format(b[1], pwFromDrmFourcc(stream->pSession->sharingData.frameInfoSHM.fmt), stream->pSession->sharingData.frameInfoSHM.w, stream->pSession->sharingData.frameInfoSHM.h, stream->pSession->sharingData.framerate, NULL, 0); @@ -1094,7 +1100,7 @@ uint32_t CPipewireConnection::buildFormatsFor(spa_pod_builder* b[2], const spa_p paramCount = 1; params[0] = build_format(b[0], pwFromDrmFourcc(stream->pSession->sharingData.frameInfoSHM.fmt), stream->pSession->sharingData.frameInfoSHM.w, - stream->pSession->sharingData.frameInfoSHM.h, stream->pSession->sharingData.framerate, NULL, 0); + stream->pSession->sharingData.frameInfoSHM.h, stream->pSession->sharingData.framerate, NULL, 0); } if (modifiers) @@ -1118,6 +1124,11 @@ CPipewireConnection::SPWStream* CPipewireConnection::streamFromSession(CScreenco void CPipewireConnection::enqueue(CScreencopyPortal::SSession* pSession) { const auto PSTREAM = streamFromSession(pSession); + if (!PSTREAM) { + Debug::log(ERR, "[pw] Attempted enqueue on invalid session??"); + return; + } + Debug::log(TRACE, "[pw] enqueue on {}", (void*)PSTREAM); if (!PSTREAM->currentPWBuffer) { @@ -1202,6 +1213,11 @@ void CPipewireConnection::enqueue(CScreencopyPortal::SSession* pSession) { void CPipewireConnection::dequeue(CScreencopyPortal::SSession* pSession) { const auto PSTREAM = streamFromSession(pSession); + if (!PSTREAM) { + Debug::log(ERR, "[pw] Attempted dequeue on invalid session??"); + return; + } + Debug::log(TRACE, "[pw] dequeue on {}", (void*)PSTREAM); const auto PWBUF = pw_stream_dequeue_buffer(PSTREAM->stream); diff --git a/src/shared/ScreencopyShared.cpp b/src/shared/ScreencopyShared.cpp index e68f6f9..0e06675 100644 --- a/src/shared/ScreencopyShared.cpp +++ b/src/shared/ScreencopyShared.cpp @@ -154,7 +154,8 @@ uint32_t drmFourccFromSHM(wl_shm_format format) { case WL_SHM_FORMAT_ARGB2101010: case WL_SHM_FORMAT_ABGR2101010: case WL_SHM_FORMAT_RGBA1010102: - case WL_SHM_FORMAT_BGRA1010102: return (uint32_t)format; + case WL_SHM_FORMAT_BGRA1010102: + case WL_SHM_FORMAT_BGR888: return (uint32_t)format; default: Debug::log(ERR, "[screencopy] Unknown format {}", (int)format); abort(); } } @@ -178,6 +179,7 @@ spa_video_format pwFromDrmFourcc(uint32_t format) { case DRM_FORMAT_ABGR2101010: return SPA_VIDEO_FORMAT_ABGR_210LE; case DRM_FORMAT_RGBA1010102: return SPA_VIDEO_FORMAT_RGBA_102LE; case DRM_FORMAT_BGRA1010102: return SPA_VIDEO_FORMAT_BGRA_102LE; + case DRM_FORMAT_BGR888: return SPA_VIDEO_FORMAT_BGR; default: Debug::log(ERR, "[screencopy] Unknown format {}", (int)format); abort(); } } diff --git a/src/shared/ToplevelManager.cpp b/src/shared/ToplevelManager.cpp index 86cfe01..880bdcd 100644 --- a/src/shared/ToplevelManager.cpp +++ b/src/shared/ToplevelManager.cpp @@ -1,5 +1,6 @@ #include "ToplevelManager.hpp" #include "../helpers/Log.hpp" +#include "../core/PortalManager.hpp" static void toplevelTitle(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, const char* title) { const auto PTL = (SToplevelHandle*)data; @@ -81,11 +82,6 @@ inline const zwlr_foreign_toplevel_manager_v1_listener managerListener = { .finished = managerFinished, }; -CToplevelManager::CToplevelManager(zwlr_foreign_toplevel_manager_v1* mgr) { - m_pManager = mgr; - zwlr_foreign_toplevel_manager_v1_add_listener(mgr, &managerListener, this); -} - bool CToplevelManager::exists(zwlr_foreign_toplevel_handle_v1* handle) { for (auto& h : m_vToplevels) { if (h->handle == handle) @@ -93,4 +89,39 @@ bool CToplevelManager::exists(zwlr_foreign_toplevel_handle_v1* handle) { } return false; +} + +CToplevelManager::CToplevelManager(wl_registry* registry, uint32_t name, uint32_t version) { + m_sWaylandConnection = {registry, name, version}; +} + +void CToplevelManager::activate() { + m_iActivateLocks++; + + Debug::log(LOG, "[toplevel] (activate) locks: {}", m_iActivateLocks); + + if (m_pManager || m_iActivateLocks < 1) + return; + + m_pManager = (zwlr_foreign_toplevel_manager_v1*)wl_registry_bind(m_sWaylandConnection.registry, m_sWaylandConnection.name, &zwlr_foreign_toplevel_manager_v1_interface, + m_sWaylandConnection.version); + zwlr_foreign_toplevel_manager_v1_add_listener(m_pManager, &managerListener, this); + wl_display_roundtrip(g_pPortalManager->m_sWaylandConnection.display); + + Debug::log(LOG, "[toplevel] Activated, bound to {:x}, toplevels: {}", (uintptr_t)m_pManager, m_vToplevels.size()); +} + +void CToplevelManager::deactivate() { + m_iActivateLocks--; + + Debug::log(LOG, "[toplevel] (deactivate) locks: {}", m_iActivateLocks); + + if (!m_pManager || m_iActivateLocks > 0) + return; + + zwlr_foreign_toplevel_manager_v1_destroy(m_pManager); + m_pManager = nullptr; + m_vToplevels.clear(); + + Debug::log(LOG, "[toplevel] unbound manager"); } \ No newline at end of file diff --git a/src/shared/ToplevelManager.hpp b/src/shared/ToplevelManager.hpp index bbf5049..de447ee 100644 --- a/src/shared/ToplevelManager.hpp +++ b/src/shared/ToplevelManager.hpp @@ -17,12 +17,23 @@ struct SToplevelHandle { class CToplevelManager { public: - CToplevelManager(zwlr_foreign_toplevel_manager_v1* mgr); + CToplevelManager(wl_registry* registry, uint32_t name, uint32_t version); + + void activate(); + void deactivate(); bool exists(zwlr_foreign_toplevel_handle_v1* handle); std::vector> m_vToplevels; private: - zwlr_foreign_toplevel_manager_v1* m_pManager; + zwlr_foreign_toplevel_manager_v1* m_pManager = nullptr; + + int64_t m_iActivateLocks = 0; + + struct { + wl_registry* registry = nullptr; + uint32_t name = 0; + uint32_t version = 0; + } m_sWaylandConnection; }; \ No newline at end of file