pax_global_header00006660000000000000000000000064152703531320014513gustar00rootroot0000000000000052 comment=d1467fea8d1895cb7835829c2fbb271a0d37760a hyprwm-xdg-desktop-portal-hyprland-d1467fe/000077500000000000000000000000001527035313200210305ustar00rootroot00000000000000hyprwm-xdg-desktop-portal-hyprland-d1467fe/.builds/000077500000000000000000000000001527035313200223705ustar00rootroot00000000000000hyprwm-xdg-desktop-portal-hyprland-d1467fe/.builds/alpine.yml000066400000000000000000000006671527035313200243740ustar00rootroot00000000000000image: alpine/edge packages: - elogind-dev - gcc - meson - pipewire-dev - wayland-dev - wayland-protocols - inih-dev - scdoc - libdrm - mesa-dev sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: - setup: | cd xdg-desktop-portal-wlr meson -Dauto_features=enabled -Dsystemd=disabled -Dsd-bus-provider=libelogind build/ - build: | cd xdg-desktop-portal-wlr ninja -C build/ hyprwm-xdg-desktop-portal-hyprland-d1467fe/.builds/archlinux.yml000066400000000000000000000010751527035313200251130ustar00rootroot00000000000000image: archlinux packages: - gcc - clang - meson - wayland - wayland-protocols - pipewire - libinih - scdoc - mesa sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: - setup: | cd xdg-desktop-portal-wlr CC=gcc meson -Dauto_features=enabled -Dsd-bus-provider=libsystemd build-gcc/ CC=clang meson -Dauto_features=enabled -Dsd-bus-provider=libsystemd build-clang/ - build-gcc: | cd xdg-desktop-portal-wlr ninja -C build-gcc/ - build-clang: | cd xdg-desktop-portal-wlr ninja -C build-clang/ hyprwm-xdg-desktop-portal-hyprland-d1467fe/.builds/freebsd.yml000066400000000000000000000007121527035313200245250ustar00rootroot00000000000000image: freebsd/latest packages: - basu - libepoll-shim - meson - pipewire - pkgconf - wayland - wayland-protocols - inih - scdoc - graphics/libdrm - graphics/mesa-libs sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: - setup: | cd xdg-desktop-portal-wlr meson -Dauto_features=enabled -Dsystemd=disabled -Dsd-bus-provider=basu build/ - build: | cd xdg-desktop-portal-wlr ninja -C build/ hyprwm-xdg-desktop-portal-hyprland-d1467fe/.clang-format000066400000000000000000000034161527035313200234070ustar00rootroot00000000000000--- Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BreakBeforeBraces: Attach BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon ColumnLimit: 180 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false IncludeBlocks: Preserve IndentCaseLabels: true IndentWidth: 4 PointerAlignment: Left ReflowComments: false SortIncludes: false SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 4 UseTab: Never AllowShortEnumsOnASingleLine: false BraceWrapping: AfterEnum: false AlignConsecutiveDeclarations: AcrossEmptyLines NamespaceIndentation: All hyprwm-xdg-desktop-portal-hyprland-d1467fe/.editorconfig000066400000000000000000000004131527035313200235030ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true indent_style = tab indent_size = 4 [*.{c,cpp,h,hpp}] indent_style = space indent_size = 4 tab_width = 4 [*.{xml,yml}] indent_style = space indent_size = 2 hyprwm-xdg-desktop-portal-hyprland-d1467fe/.github/000077500000000000000000000000001527035313200223705ustar00rootroot00000000000000hyprwm-xdg-desktop-portal-hyprland-d1467fe/.github/vouch_pr_message.md000066400000000000000000000005431527035313200262450ustar00rootroot00000000000000Hi @{author}, Thanks for your contribution! Unfortunately, Hyprland and hypr* no longer accept contributions from unvouched contributors. To get vouched, please see our contributing guide at [wiki.hypr.land/Contributing-and-Debugging](https://wiki.hypr.land/Contributing-and-Debugging/) This PR has been closed automatically and will not be re-opened. hyprwm-xdg-desktop-portal-hyprland-d1467fe/.github/workflows/000077500000000000000000000000001527035313200244255ustar00rootroot00000000000000hyprwm-xdg-desktop-portal-hyprland-d1467fe/.github/workflows/nix-build.yaml000066400000000000000000000005421527035313200272050ustar00rootroot00000000000000name: Build xdph (Nix) on: [push, pull_request, workflow_dispatch] jobs: nix: if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) uses: hyprwm/actions/.github/workflows/nix.yml@main secrets: inherit with: command: nix build --print-build-logs --extra-substituters "https://hyprland.cachix.org" hyprwm-xdg-desktop-portal-hyprland-d1467fe/.github/workflows/vouch-prs.yml000066400000000000000000000021171527035313200270770ustar00rootroot00000000000000name: Vouch MR Check on: pull_request_target: types: [opened, reopened] permissions: contents: read pull-requests: write jobs: check-vouch: runs-on: ubuntu-latest steps: - name: Check out template uses: actions/checkout@v6 with: persist-credentials: false - name: Verify central Vouch list env: GH_TOKEN: ${{ github.token }} run: | gh api \ "/repos/${{ github.repository_owner }}/.github/contents/vouch/VOUCHED.td?ref=${{ github.event.repository.default_branch }}" \ >/dev/null - name: Enforce central Vouch list uses: mitchellh/vouch/action/check-pr@v1 with: pr-number: ${{ github.event.pull_request.number }} repo: ${{ github.repository }} vouched-repo: ${{ github.repository_owner }}/.github vouched-file: vouch/VOUCHED.td template-file: .github/vouch_pr_message.md require-vouch: true auto-close: true dry-run: false env: GITHUB_TOKEN: ${{ github.token }}hyprwm-xdg-desktop-portal-hyprland-d1467fe/.gitignore000066400000000000000000000011721527035313200230210ustar00rootroot00000000000000# Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf # Build folders build/ build-*/ hyprland-share-picker/build/ # Generated code files protocols/*.c* protocols/*.h* # Nix build results result result-man # Code editors .vscode/ # MISCELLANEOUS .cache hyprwm-xdg-desktop-portal-hyprland-d1467fe/.gitmodules000066400000000000000000000003601527035313200232040ustar00rootroot00000000000000[submodule "hyprland-protocols"] path = subprojects/hyprland-protocols url = https://github.com/hyprwm/hyprland-protocols [submodule "subprojects/sdbus-cpp"] path = subprojects/sdbus-cpp url = https://github.com/Kistler-Group/sdbus-cpp hyprwm-xdg-desktop-portal-hyprland-d1467fe/AGENTS.md000066400000000000000000000060241527035313200223350ustar00rootroot00000000000000# AGENTS.md ## Review guidelines - Prioritize correctness, lack of regressions, performance, API stability, and code clarity and readability. - For performance-sensitive paths, flag obvious algorithmic regressions or slow paths. - For tests, flag missing coverage for changed or new behavior, as long as the testing framework is capable of testing it. - For config changes (new / removed options) remind the author to make a separate wiki PR if they haven't linked one yet. - Flag silent config breakage: e.g. changing an existing option's behavior. This is not allowed. - Flag bad config style that breaks this project's style guidelines (further below) and suggest fixes. - Flag bad code approaches that break this project's core code guidelines (further below) and suggest improvements. ## Style guidelines - Code must be clang-formatted according to `.clang-format`. - single-line if and else statements must come without braces. This rule applies only to if / else, not do / while / other. - Avoid function bodies in headers as much as possible. - Avoid namespace {} in source files to mark local functions. Prefer `static`. - Prefer guards in functions and loops: `if (!cond) continue;` - Prefer forward-declaration in headers to inclusion. - Leave a stray `,` at the end of brace-enclosed lists to make formatting easier to read. - Leave a `;` inside empty function bodies for formatting. - Naming conventions: - class: `CMyClass` - struct: `SMyStruct` - interface: `IMyInterface` - Do not use absolute includes from `src/` in headers: instead of `#include "a/b.hpp"` use `#include "../a/b.hpp"` for example. Protocol headers do not require this. ## Core code guidelines - Stick to good code practices: - Avoid complex classes / functions, prefer SRP. - Consider using an observer pattern via hyprutils Signals where appropriate. - Consider classic OOP patterns where appropriate: Strategy, Singleton, Proxy, etc. - Watch out for typical bad practices in code: feature envy, LSP, etc. - Use templating and inheritance to clean up code where appropriate. - For obtaining singletons, use a `UP& myClass();` pattern inside a namespace. This can be implemented in source as making and returning a static ptr. - Do not, under any circumstance: - `using namespace std;` - leave uninitialized primitives (int, float, etc) - Avoid, unless absolutely necessary: - the C standard library. Use the C++ STL. - `malloc` / `free` / etc - C-style pointers. Use SP<> WP<> and UP<> from hyprutils. These are Shared, Weak and Unique pointers respectively. C-style pointers may be used in select scenarios (e.g. destroying fns, where it's impossible to make a mistake) but everywhere else must not be used unless necessary. - C-style casts. Use rc<>, sc<>, or cc<> from hyprutils. These are shorthands to equivalent C++ casts. - Avoid: - violating clang-tidy (`.clang-tidy`) - manual C-style cleanup: `some_c_thing_new()` and `some_c_thing_free()` can be wrapped. - Make sure to write tests for code which our Unit (`tests/`) or Integration (`hyprtester/`) tests can test. hyprwm-xdg-desktop-portal-hyprland-d1467fe/CMakeLists.txt000066400000000000000000000137721527035313200236020ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.19) file(READ ${CMAKE_CURRENT_SOURCE_DIR}/VERSION VER) string(STRIP ${VER} VER) project( xdg-desktop-portal-hyprland DESCRIPTION "An XDG-Destop-Portal backend for Hyprland (and wlroots)" VERSION ${VER}) include(CTest) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_MESSAGE_LOG_LEVEL "STATUS") set(SYSTEMD_SERVICES ON CACHE BOOL "Install systemd service file") if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring XDPH in Debug with CMake") add_compile_definitions(HYPRLAND_DEBUG) else() add_compile_options(-O3) message(STATUS "Configuring XDPH in Release with CMake") endif() add_compile_definitions(XDPH_VERSION="${VER}") # configure include(GNUInstallDirs) set(LIBEXECDIR ${CMAKE_INSTALL_FULL_LIBEXECDIR}) configure_file(org.freedesktop.impl.portal.desktop.hyprland.service.in org.freedesktop.impl.portal.desktop.hyprland.service @ONLY) if(SYSTEMD_SERVICES) configure_file(contrib/systemd/xdg-desktop-portal-hyprland.service.in contrib/systemd/xdg-desktop-portal-hyprland.service @ONLY) endif() 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> -Wno-address-of-temporary) # dependencies message(STATUS "Checking deps...") add_subdirectory(hyprland-share-picker) find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules( deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols>=1.39 libpipewire-0.3>=1.1.82 libspa-0.2 libdrm uuid gbm hyprlang>=0.2.0 hyprutils>=0.2.6 hyprwayland-scanner>=0.4.2) # check whether we can find sdbus-c++ through pkg-config pkg_check_modules(SDBUS IMPORTED_TARGET sdbus-c++>=2.0.0) if(NOT SDBUS_FOUND) include_directories("subprojects/sdbus-cpp/include/") add_subdirectory(subprojects/sdbus-cpp EXCLUDE_FROM_ALL) add_library(PkgConfig::SDBUS ALIAS sdbus-c++) endif() # same for hyprland-protocols pkg_check_modules(HYPRLAND_PROTOS IMPORTED_TARGET hyprland-protocols>=0.8.0) if(HYPRLAND_PROTOS_FOUND) set(HYPRLAND_PROTOCOLS "${HYPRLAND_PROTOS_PREFIX}/share/hyprland-protocols") else() set(HYPRLAND_PROTOCOLS "${CMAKE_SOURCE_DIR}/subprojects/hyprland-protocols") endif() if(NOT EXISTS "${HYPRLAND_PROTOCOLS}/protocols/hyprland-workspace-image-capture-source-v1.xml") message( FATAL_ERROR "Missing ${HYPRLAND_PROTOCOLS}/protocols/hyprland-workspace-image-capture-source-v1.xml. " "Workspace capture requires a hyprland-protocols revision containing this protocol.") endif() file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") add_executable(xdg-desktop-portal-hyprland ${SRCFILES}) target_include_directories(xdg-desktop-portal-hyprland PRIVATE "src/" "protocols/") target_link_libraries( xdg-desktop-portal-hyprland PRIVATE rt PkgConfig::SDBUS Threads::Threads PkgConfig::deps) # protocols pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") pkg_get_variable(WAYLAND_SCANNER_DIR wayland-scanner pkgdatadir) message(STATUS "Found wayland-scanner at ${WAYLAND_SCANNER_DIR}") function(protocolnew protoPath protoName external) if(external) set(path ${protoPath}) else() set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath}) endif() add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(xdg-desktop-portal-hyprland PRIVATE protocols/${protoName}.cpp protocols/${protoName}.hpp) endfunction() function(protocolWayland) add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp COMMAND hyprwayland-scanner --wayland-enums --client ${WAYLAND_SCANNER_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(xdg-desktop-portal-hyprland PRIVATE protocols/wayland.cpp protocols/wayland.hpp) endfunction() protocolwayland() protocolnew("${CMAKE_SOURCE_DIR}/protocols" "wlr-foreign-toplevel-management-unstable-v1" true) protocolnew("${CMAKE_SOURCE_DIR}/protocols" "wlr-screencopy-unstable-v1" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-global-shortcuts-v1" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-toplevel-export-v1" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-toplevel-mapping-v1" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-input-capture-v1" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-workspace-image-capture-source-v1" true) protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false) protocolnew("unstable/xdg-output" "xdg-output-unstable-v1" false) protocolnew("staging/ext-foreign-toplevel-list" "ext-foreign-toplevel-list-v1" false) protocolnew("staging/ext-image-copy-capture" "ext-image-copy-capture-v1" false) protocolnew("staging/ext-image-capture-source" "ext-image-capture-source-v1" false) protocolnew("staging/ext-workspace" "ext-workspace-v1" false) # Installation install(TARGETS xdg-desktop-portal-hyprland DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}) install(FILES hyprland.portal DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/xdg-desktop-portal/portals") install( FILES ${CMAKE_BINARY_DIR}/org.freedesktop.impl.portal.desktop.hyprland.service DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services") if(SYSTEMD_SERVICES) install( FILES ${CMAKE_BINARY_DIR}/contrib/systemd/xdg-desktop-portal-hyprland.service DESTINATION "lib/systemd/user") endif() hyprwm-xdg-desktop-portal-hyprland-d1467fe/CONTRIBUTING.md000066400000000000000000000022611527035313200232620ustar00rootroot00000000000000# Contributing We closely follow the wlroots [contributing] guidelines where possible. Please see that document for more information. ## Tooling Useful tools include `dbus-monitor` to watch requests being made, and `dbus-send` and the similar `busctl call` for manual dbus calls. You can test the integration with the [portal-test] Flatpak app. Alternatively you can trigger it with [trigger-screen-shot.py] and [xdp-screen-cast.py]. [contributing]: https://github.com/swaywm/wlroots/blob/master/CONTRIBUTING.md [portal-test]: https://github.com/matthiasclasen/portal-test [trigger-screen-shot.py]: https://gist.github.com/danshick/3446dac24c64ce6172eced4ac255ac3d [xdp-screen-cast.py]: https://gitlab.gnome.org/snippets/19 ## Alternate *.portal Location xdg-desktop-portal will read the XDG_DESKTOP_PORTAL_DIR environment variable for an alternate path for *.portal files. This can be useful when testing changes to that portal file, or for testing xdpw without installing it. This feature is undocumented and shouldn't be relied on, but may be helpful in some circumstances. https://github.com/flatpak/xdg-desktop-portal/blob/e7f78640e35debb68fef891fc233c449006d9724/src/portal-impl.c#L124 hyprwm-xdg-desktop-portal-hyprland-d1467fe/LICENSE000066400000000000000000000027531527035313200220440ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2023, vaxerski All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.hyprwm-xdg-desktop-portal-hyprland-d1467fe/README.md000066400000000000000000000025741527035313200223170ustar00rootroot00000000000000# xdg-desktop-portal-hyprland An [XDG Desktop Portal](https://github.com/flatpak/xdg-desktop-portal) backend for Hyprland. ## Installing First, make sure to install the required dependencies: ``` gbm hyprland-protocols hyprlang hyprutils hyprwayland-scanner libdrm libpipewire-0.3 libspa-0.2 sdbus-cpp wayland-client wayland-protocols ``` Then run the build and install command: ```sh git clone --recursive https://github.com/hyprwm/xdg-desktop-portal-hyprland cd xdg-desktop-portal-hyprland/ cmake -DCMAKE_INSTALL_LIBEXECDIR=/usr/lib -DCMAKE_INSTALL_PREFIX=/usr -B build cmake --build build sudo cmake --install build ``` ## Nix > [!CAUTION] > XDPH should not be used from this flake directly! > > Instead, use it from the [Hyprland flake](https://github.com/hyprwm/Hyprland). There are two reasons for the above: 1. Hyprland depends on XDPH, but XDPH also depends on Hyprland. This results in a cyclic dependency, which is a nightmare. To counter this, we use the Nixpkgs Hyprland package in this flake, so that it can be later consumed by the Hyprland flake while overriding the Hyprland package. 2. Even if you manually do all the overriding, you may still get it wrong and lose out on the Cachix cache (which has XDPH as exposed by the Hyprland flake). ## Running, FAQs, etc. See [the Hyprland wiki](https://wiki.hyprland.org/Hypr-Ecosystem/xdg-desktop-portal-hyprland/) hyprwm-xdg-desktop-portal-hyprland-d1467fe/VERSION000066400000000000000000000000061527035313200220740ustar00rootroot000000000000001.5.0 hyprwm-xdg-desktop-portal-hyprland-d1467fe/contrib/000077500000000000000000000000001527035313200224705ustar00rootroot00000000000000hyprwm-xdg-desktop-portal-hyprland-d1467fe/contrib/config.sample000066400000000000000000000001251527035313200251360ustar00rootroot00000000000000[screencast] output_name= max_fps=30 chooser_cmd=slurp -f %o -or chooser_type=simple hyprwm-xdg-desktop-portal-hyprland-d1467fe/contrib/systemd/000077500000000000000000000000001527035313200241605ustar00rootroot00000000000000hyprwm-xdg-desktop-portal-hyprland-d1467fe/contrib/systemd/xdg-desktop-portal-hyprland.service.in000066400000000000000000000005041527035313200335150ustar00rootroot00000000000000[Unit] Description=Portal service (Hyprland implementation) PartOf=graphical-session.target After=graphical-session.target ConditionEnvironment=WAYLAND_DISPLAY [Service] Type=dbus BusName=org.freedesktop.impl.portal.desktop.hyprland ExecStart=@LIBEXECDIR@/xdg-desktop-portal-hyprland Restart=on-failure Slice=session.slice hyprwm-xdg-desktop-portal-hyprland-d1467fe/flake.lock000066400000000000000000000131371527035313200227710ustar00rootroot00000000000000{ "nodes": { "aquamarine": { "inputs": { "hyprutils": [ "hyprutils" ], "hyprwayland-scanner": [ "hyprwayland-scanner" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1791041429, "narHash": "sha256-Y/Q4JtMo6nHB6XS1LPSfK7Y4AnuNl9jci/5sCps1MRc=", "owner": "hyprwm", "repo": "aquamarine", "rev": "52ea11f06dd492f34b3cc0b39a9c805beecafcff", "type": "github" }, "original": { "owner": "hyprwm", "repo": "aquamarine", "type": "github" } }, "hyprgraphics": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1791066858, "narHash": "sha256-hLh6ALHrGpgYm8viWCo1pO+ErC22YDGdAvVPcfYsXA8=", "owner": "hyprwm", "repo": "hyprgraphics", "rev": "80c7d00f2236732853e390019ff8d79d17fa3612", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprgraphics", "type": "github" } }, "hyprland-protocols": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1792149240, "narHash": "sha256-g09dB6NCTzD4F6YgYrGXRhUfG52xwxUtlMAbHvv/1gE=", "owner": "hyprwm", "repo": "hyprland-protocols", "rev": "044a434ec371f985655389bcef09286b1af3d77c", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprland-protocols", "type": "github" } }, "hyprlang": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1786464129, "narHash": "sha256-339AkTlpMYSIvFuG0rnR+8Yg4/AZKeJalshJavlnKfg=", "owner": "hyprwm", "repo": "hyprlang", "rev": "9508458be316a0d70d37ebed1ab725ccd10411ff", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprlang", "type": "github" } }, "hyprtoolkit": { "inputs": { "aquamarine": [ "aquamarine" ], "hyprgraphics": [ "hyprgraphics" ], "hyprlang": [ "hyprlang" ], "hyprutils": [ "hyprutils" ], "hyprwayland-scanner": [ "hyprwayland-scanner" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1789562890, "narHash": "sha256-LFzeVJkDMQLjCpi55S9oc3N/wMrW2RCS1ShPL+1HB78=", "owner": "hyprwm", "repo": "hyprtoolkit", "rev": "b3f2d0e725e4eaf804af7e529cb69bbed43d1a04", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprtoolkit", "type": "github" } }, "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1789968180, "narHash": "sha256-YGRfhw+fJW7oKW3IspoceD34jeokN1KXNxKU6APlFH4=", "owner": "hyprwm", "repo": "hyprutils", "rev": "95983ee836ff205e615bba67d1f67098e1231941", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1786464033, "narHash": "sha256-QM8Qe4/L8lpdVN4bgwahmi+jyyc4fisseDMe4afcDxA=", "owner": "hyprwm", "repo": "hyprwayland-scanner", "rev": "62e62c1ca23da17612c6890d4ad2064f575643db", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprwayland-scanner", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1791048980, "narHash": "sha256-KgItSKML8Xvte0B7/uGnBDsYzSnnKHcOaiUbgWBXLXw=", "owner": "NixOS", "repo": "nixpkgs", "rev": "a7868a727837f3c09cee2ce0ca671c76b1589fed", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "aquamarine": "aquamarine", "hyprgraphics": "hyprgraphics", "hyprland-protocols": "hyprland-protocols", "hyprlang": "hyprlang", "hyprtoolkit": "hyprtoolkit", "hyprutils": "hyprutils", "hyprwayland-scanner": "hyprwayland-scanner", "nixpkgs": "nixpkgs", "systems": "systems" } }, "systems": { "locked": { "lastModified": 1689347949, "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", "owner": "nix-systems", "repo": "default-linux", "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default-linux", "type": "github" } } }, "root": "root", "version": 7 } hyprwm-xdg-desktop-portal-hyprland-d1467fe/flake.nix000066400000000000000000000047441527035313200226430ustar00rootroot00000000000000{ description = "xdg-desktop-portal-hyprland"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; # systems.url = "github:nix-systems/default-linux"; aquamarine = { url = "github:hyprwm/aquamarine"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; inputs.hyprutils.follows = "hyprutils"; inputs.hyprwayland-scanner.follows = "hyprwayland-scanner"; }; hyprgraphics = { url = "github:hyprwm/hyprgraphics"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; inputs.hyprutils.follows = "hyprutils"; }; hyprland-protocols = { url = "github:hyprwm/hyprland-protocols"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprlang = { url = "github:hyprwm/hyprlang"; inputs.hyprutils.follows = "hyprutils"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprutils = { url = "github:hyprwm/hyprutils"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprtoolkit = { url = "github:hyprwm/hyprtoolkit"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; inputs.aquamarine.follows = "aquamarine"; inputs.hyprgraphics.follows = "hyprgraphics"; inputs.hyprlang.follows = "hyprlang"; inputs.hyprutils.follows = "hyprutils"; inputs.hyprwayland-scanner.follows = "hyprwayland-scanner"; }; hyprwayland-scanner = { url = "github:hyprwm/hyprwayland-scanner"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; }; outputs = { self, nixpkgs, systems, ... }@inputs: let inherit (nixpkgs) lib; eachSystem = lib.genAttrs (import systems); pkgsFor = eachSystem ( system: import nixpkgs { localSystem = system; overlays = [ self.overlays.xdg-desktop-portal-hyprland-with-deps ]; } ); in { overlays = import ./nix/overlays.nix { inherit self inputs lib; }; packages = eachSystem (system: { inherit (pkgsFor.${system}) xdg-desktop-portal-hyprland sdbus-cpp_2; default = self.packages.${system}.xdg-desktop-portal-hyprland; }); formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.nixfmt-tree); }; } hyprwm-xdg-desktop-portal-hyprland-d1467fe/hyprland-share-picker/000077500000000000000000000000001527035313200252245ustar00rootroot00000000000000hyprwm-xdg-desktop-portal-hyprland-d1467fe/hyprland-share-picker/CMakeLists.txt000066400000000000000000000016101527035313200277620ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.19) project( hyprland-share-picker VERSION 0.1 LANGUAGES CXX) include(GNUInstallDirs) include(CTest) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(PkgConfig REQUIRED) pkg_check_modules( picker_deps REQUIRED IMPORTED_TARGET hyprtoolkit>=0.6.0 hyprutils>=0.2.6 libdrm) add_executable(hyprland-share-picker main.cpp Picker.cpp PickerData.cpp RegionPicker.cpp) target_link_libraries(hyprland-share-picker PRIVATE PkgConfig::picker_deps) install(TARGETS hyprland-share-picker RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) if(BUILD_TESTING) add_executable(hyprland-share-picker-data-test PickerDataTest.cpp PickerData.cpp) add_test(NAME hyprland-share-picker-data COMMAND hyprland-share-picker-data-test) endif() hyprwm-xdg-desktop-portal-hyprland-d1467fe/hyprland-share-picker/Makefile000066400000000000000000000002631527035313200266650ustar00rootroot00000000000000 all: mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja cmake --build ./build --config Release --target all -j$(shell nproc)hyprwm-xdg-desktop-portal-hyprland-d1467fe/hyprland-share-picker/Picker.cpp000066400000000000000000000353501527035313200271530ustar00rootroot00000000000000#include "Picker.hpp" #include "PickerData.hpp" #include "RegionPicker.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Hyprtoolkit; using namespace Hyprutils::Memory; static constexpr float SOURCE_BUTTON_HEIGHT = 42.F; static CDynamicSize fullSize() { return {CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}; } static CSharedPointer makeText(const CSharedPointer& backend, std::string text, CFontSize fontSize = {CFontSize::HT_FONT_TEXT}) { return CTextBuilder::begin()->text(std::move(text))->fontSize(std::move(fontSize))->color([backend] { return backend->getPalette()->m_colors.text; })->commence(); } CPicker::CPicker(const CSharedPointer& backend, std::vector outputs, std::vector windows, std::vector workspaces, bool allowTokenByDefault) : m_backend(backend), m_outputs(std::move(outputs)), m_windows(std::move(windows)), m_workspaces(std::move(workspaces)), m_allowTokenByDefault(allowTokenByDefault) { for (const auto& toolkitOutput : m_backend->getOutputs()) { const auto MATCH = std::ranges::find_if(m_outputs, [&toolkitOutput](const auto& output) { return output.name == toolkitOutput->port(); }); if (MATCH != m_outputs.end()) { MATCH->description = toolkitOutput->desc(); continue; } m_outputs.emplace_back(SOutputEntry{ .name = toolkitOutput->port(), .description = toolkitOutput->desc(), }); } } CPicker::~CPicker() = default; bool CPicker::initialize() { m_window = CWindowBuilder::begin() ->type(HT_WINDOW_TOPLEVEL) ->preferredSize({600, 400}) ->minSize({500, 320}) ->maxSize({1280, 800}) ->resizable(true) ->appTitle("Select what to share") ->appClass("hyprland-share-picker") ->commence(); if (!m_window) return false; buildUI(); m_window->m_events.closeRequest.listenStatic([this] { cancel(); }); return true; } void CPicker::run() { m_window->open(); m_backend->enterLoop(); } void CPicker::buildUI() { const auto BACKGROUND = CRectangleBuilder::begin()->color([this] { return m_backend->getPalette()->m_colors.background; })->size(fullSize())->commence(); m_window->m_rootElement->addChild(BACKGROUND); m_mainLayout = CColumnLayoutBuilder::begin()->size(fullSize())->gap(10)->commence(); m_mainLayout->setMargin(14); BACKGROUND->addChild(m_mainLayout); m_mainLayout->addChild(makeText(m_backend, "Select what to share", {CFontSize::HT_FONT_H2})); m_mainLayout->addChild(makeText(m_backend, "Choose a monitor, window, workspace, or region to share.")); buildTabs(); m_contentHost = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {1.F, 10.F}})->commence(); m_contentHost->setGrow(true); m_mainLayout->addChild(m_contentHost); const auto TOKEN_ROW = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1.F, 1.F}})->gap(8)->commence(); m_allowToken = CCheckboxBuilder::begin()->toggled(m_allowTokenByDefault)->commence(); TOKEN_ROW->addChild(m_allowToken); const auto TOKEN_LABEL = makeText(m_backend, "Remember this choice for future requests"); TOKEN_ROW->addChild(TOKEN_LABEL); m_mainLayout->addChild(TOKEN_ROW); const auto ACTION_ROW = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1.F, 1.F}})->gap(8)->commence(); const auto SPACER = CNullBuilder::begin()->commence(); SPACER->setGrow(true); ACTION_ROW->addChild(SPACER); ACTION_ROW->addChild(CButtonBuilder::begin()->label("Cancel")->onMainClick([this](CSharedPointer) { cancel(); })->commence()); m_shareButton = CButtonBuilder::begin()->label("Share")->accent(true)->enabled(false)->onMainClick([this](CSharedPointer) { submit(); })->commence(); ACTION_ROW->addChild(m_shareButton); m_mainLayout->addChild(ACTION_ROW); rebuildContent(); } void CPicker::buildTabs() { const auto TAB_ROW = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {1.F, 36.F}})->commence(); static constexpr std::array LABELS = {"Monitors", "Windows", "Workspaces", "Region"}; m_tabButtons.reserve(LABELS.size()); for (size_t i = 0; i < LABELS.size(); ++i) { auto button = CButtonBuilder::begin() ->label(LABELS[i]) ->accent(i == m_activeTab) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F / LABELS.size(), 1.F}}) ->onMainClick([this, i](CSharedPointer) { switchTab(static_cast(i)); }) ->commence(); m_tabButtons.emplace_back(button); TAB_ROW->addChild(button); } m_mainLayout->addChild(TAB_ROW); } void CPicker::rebuildContent() { m_contentHost->clearChildren(); m_sourceButtons.clear(); switch (m_activeTab) { case PICKER_TAB_OUTPUTS: buildOutputContent(); break; case PICKER_TAB_WINDOWS: buildWindowContent(); break; case PICKER_TAB_WORKSPACE: buildWorkspaceContent(); break; case PICKER_TAB_REGION: buildRegionContent(); break; } } void CPicker::buildOutputContent() { if (m_outputs.empty()) { const auto TEXT = makeText(m_backend, "No monitors are available."); TEXT->setPositionMode(IElement::HT_POSITION_ABSOLUTE); TEXT->setPositionFlag(IElement::HT_POSITION_FLAG_CENTER, true); m_contentHost->addChild(TEXT); return; } const auto SCROLL = CScrollAreaBuilder::begin()->scrollY(true)->showScrollbar(true)->size(fullSize())->commence(); const auto LIST = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1.F, 1.F}})->gap(6)->commence(); SCROLL->addChild(LIST); m_contentHost->addChild(SCROLL); m_sourceButtons.reserve(m_outputs.size()); for (size_t i = 0; i < m_outputs.size(); ++i) { const auto& OUTPUT = m_outputs[i]; std::string label = OUTPUT.name; if (!OUTPUT.description.empty() && OUTPUT.description != OUTPUT.name) label += ": " + OUTPUT.description; if (OUTPUT.hasGeometry) label += std::format(" ({}×{} at [{}, {}])", OUTPUT.width, OUTPUT.height, OUTPUT.x, OUTPUT.y); const auto ESCAPED_LABEL = escapeMarkup(label); auto button = CButtonBuilder::begin() ->label(std::string{ESCAPED_LABEL}) ->alignText(HT_FONT_ALIGN_LEFT) ->ellipsize(true) ->accent(m_selectedSource == i && m_selection && m_selection->type == SELECTION_OUTPUT) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {1.F, SOURCE_BUTTON_HEIGHT}}) ->onMainClick([this, i](CSharedPointer) { selectOutput(i); }) ->commence(); m_sourceButtons.emplace_back(button); LIST->addChild(button); } } void CPicker::buildWindowContent() { if (m_windows.empty()) { const auto TEXT = makeText(m_backend, "No shareable windows are available."); TEXT->setPositionMode(IElement::HT_POSITION_ABSOLUTE); TEXT->setPositionFlag(IElement::HT_POSITION_FLAG_CENTER, true); m_contentHost->addChild(TEXT); return; } const auto SCROLL = CScrollAreaBuilder::begin()->scrollY(true)->showScrollbar(true)->size(fullSize())->commence(); const auto LIST = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1.F, 1.F}})->gap(6)->commence(); SCROLL->addChild(LIST); m_contentHost->addChild(SCROLL); m_sourceButtons.reserve(m_windows.size()); for (size_t i = 0; i < m_windows.size(); ++i) { const auto& WINDOW = m_windows[i]; std::string label = WINDOW.title.empty() ? WINDOW.windowClass : WINDOW.title; if (!WINDOW.windowClass.empty() && WINDOW.windowClass != label) label += ": " + WINDOW.windowClass; const auto ESCAPED_LABEL = escapeMarkup(label); auto button = CButtonBuilder::begin() ->label(std::string{ESCAPED_LABEL}) ->alignText(HT_FONT_ALIGN_LEFT) ->ellipsize(true) ->accent(m_selectedSource == i && m_selection && m_selection->type == SELECTION_WINDOW) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {1.F, SOURCE_BUTTON_HEIGHT}}) ->onMainClick([this, i](CSharedPointer) { selectWindow(i); }) ->commence(); m_sourceButtons.emplace_back(button); LIST->addChild(button); } } void CPicker::buildWorkspaceContent() { if (m_workspaces.empty()) { const auto TEXT = makeText(m_backend, "No shareable workspaces are available."); TEXT->setPositionMode(IElement::HT_POSITION_ABSOLUTE); TEXT->setPositionFlag(IElement::HT_POSITION_FLAG_CENTER, true); m_contentHost->addChild(TEXT); return; } const auto SCROLL = CScrollAreaBuilder::begin()->scrollY(true)->showScrollbar(true)->size(fullSize())->commence(); const auto LIST = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1.F, 1.F}})->gap(6)->commence(); SCROLL->addChild(LIST); m_contentHost->addChild(SCROLL); m_sourceButtons.reserve(m_workspaces.size()); for (size_t i = 0; i < m_workspaces.size(); ++i) { const auto& WORKSPACE = m_workspaces[i]; const auto ESCAPED_LABEL = escapeMarkup(workspaceLabel(WORKSPACE)); auto button = CButtonBuilder::begin() ->label(std::string{ESCAPED_LABEL}) ->alignText(HT_FONT_ALIGN_LEFT) ->ellipsize(true) ->accent(m_selectedSource == i && m_selection && m_selection->type == SELECTION_WORKSPACE) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {1.F, SOURCE_BUTTON_HEIGHT}}) ->onMainClick([this, i](CSharedPointer) { selectWorkspace(i); }) ->commence(); m_sourceButtons.emplace_back(button); LIST->addChild(button); } } void CPicker::buildRegionContent() { const auto LAYOUT = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1.F, 1.F}})->gap(10)->commence(); LAYOUT->setMargin(8); m_contentHost->addChild(LAYOUT); LAYOUT->addChild(makeText(m_backend, "Select an area of the screen. The picker will temporarily hide while you draw the region.")); const bool HAS_OUTPUTS = !m_outputs.empty(); if (!HAS_OUTPUTS) LAYOUT->addChild(makeText(m_backend, "Region selection is unavailable because no monitors were found.")); if (m_selection && m_selection->type == SELECTION_REGION) LAYOUT->addChild(makeText( m_backend, std::format("Selected: {}, {}×{} at [{}, {}]", escapeMarkup(m_selection->output), m_selection->width, m_selection->height, m_selection->x, m_selection->y))); LAYOUT->addChild(CButtonBuilder::begin() ->label(m_selection && m_selection->type == SELECTION_REGION ? "Choose another region…" : "Choose region…") ->enabled(!m_regionSelecting && HAS_OUTPUTS) ->onMainClick([this](CSharedPointer) { beginRegionSelection(); }) ->commence()); } void CPicker::updateSelectionButtons() { for (size_t i = 0; i < m_sourceButtons.size(); ++i) m_sourceButtons[i]->rebuild()->accent(m_selectedSource == i)->commence(); m_shareButton->setEnabled(m_selection.has_value()); } void CPicker::switchTab(ePickerTab tab) { if (tab == m_activeTab) return; m_activeTab = tab; m_selection.reset(); m_selectedSource.reset(); m_shareButton->setEnabled(false); for (size_t i = 0; i < m_tabButtons.size(); ++i) m_tabButtons[i]->rebuild()->accent(i == m_activeTab)->commence(); rebuildContent(); } void CPicker::selectOutput(size_t index) { if (index >= m_outputs.size()) return; m_selection = SSelection{ .type = SELECTION_OUTPUT, .output = m_outputs[index].name, }; m_selectedSource = index; updateSelectionButtons(); } void CPicker::selectWindow(size_t index) { if (index >= m_windows.size()) return; m_selection = SSelection{ .type = SELECTION_WINDOW, .windowID = m_windows[index].id, }; m_selectedSource = index; updateSelectionButtons(); } void CPicker::selectWorkspace(size_t index) { if (index >= m_workspaces.size()) return; m_selection = SSelection{ .type = SELECTION_WORKSPACE, .workspaceID = m_workspaces[index].id, }; m_selectedSource = index; updateSelectionButtons(); } void CPicker::beginRegionSelection() { if (m_regionSelecting) return; m_regionSelecting = true; m_window->close(); m_backend->addIdle([this] { const auto REGION = selectRegion(m_outputs); m_regionSelecting = false; if (REGION) m_selection = *REGION; rebuildContent(); m_shareButton->setEnabled(m_selection.has_value()); m_window->open(); }); } void CPicker::submit() { if (!m_selection) return; std::cout << formatSelection(*m_selection, m_allowToken->state()) << std::flush; m_window->close(); m_backend->destroy(); } void CPicker::cancel() { m_window->close(); m_backend->destroy(); } hyprwm-xdg-desktop-portal-hyprland-d1467fe/hyprland-share-picker/Picker.hpp000066400000000000000000000075261527035313200271640ustar00rootroot00000000000000#pragma once #include "PickerData.hpp" #include #include #include #include #include #include namespace Hyprtoolkit { class IBackend; class IWindow; class CButtonElement; class CCheckboxElement; class CColumnLayoutElement; class CNullElement; } class CPicker { public: CPicker(const Hyprutils::Memory::CSharedPointer& backend, std::vector outputs, std::vector windows, std::vector workspaces, bool allowTokenByDefault); ~CPicker(); bool initialize(); void run(); private: enum ePickerTab : uint8_t { PICKER_TAB_OUTPUTS = 0, PICKER_TAB_WINDOWS, PICKER_TAB_WORKSPACE, PICKER_TAB_REGION, }; void buildUI(); void buildTabs(); void rebuildContent(); void buildOutputContent(); void buildWindowContent(); void buildWorkspaceContent(); void buildRegionContent(); void updateSelectionButtons(); void switchTab(ePickerTab tab); void selectOutput(size_t index); void selectWindow(size_t index); void selectWorkspace(size_t index); void beginRegionSelection(); void submit(); void cancel(); Hyprutils::Memory::CSharedPointer m_backend; Hyprutils::Memory::CSharedPointer m_window; Hyprutils::Memory::CSharedPointer m_mainLayout; Hyprutils::Memory::CSharedPointer m_contentHost; Hyprutils::Memory::CSharedPointer m_allowToken; Hyprutils::Memory::CSharedPointer m_shareButton; std::vector> m_tabButtons; std::vector> m_sourceButtons; std::vector m_outputs; std::vector m_windows; std::vector m_workspaces; std::optional m_selection; std::optional m_selectedSource; ePickerTab m_activeTab = PICKER_TAB_OUTPUTS; bool m_regionSelecting = false; bool m_allowTokenByDefault = false; }; hyprwm-xdg-desktop-portal-hyprland-d1467fe/hyprland-share-picker/PickerData.cpp000066400000000000000000000215311527035313200277410ustar00rootroot00000000000000#include "PickerData.hpp" #include #include #include #include static bool parseUint32(std::string_view value, uint32_t& result) { if (value.empty()) return false; const auto PARSED = std::from_chars(value.data(), value.data() + value.size(), result); return PARSED.ec == std::errc{} && PARSED.ptr == value.data() + value.size(); } static bool parseInt32(std::string_view value, int32_t& result) { if (value.empty()) return false; const auto PARSED = std::from_chars(value.data(), value.data() + value.size(), result); return PARSED.ec == std::errc{} && PARSED.ptr == value.data() + value.size(); } static std::optional takeUntil(std::string_view& value, std::string_view separator) { const auto POS = value.find(separator); if (POS == std::string_view::npos) return std::nullopt; const auto RESULT = value.substr(0, POS); value.remove_prefix(POS + separator.size()); return RESULT; } std::vector parseWindowList(std::string_view list) { std::vector result; while (!list.empty()) { const auto ID = takeUntil(list, "[HC>]"); const auto CLASS = takeUntil(list, "[HT>]"); const auto TITLE = takeUntil(list, "[HE>]"); const auto ADDR = takeUntil(list, "[HA>]"); if (!ID || !CLASS || !TITLE || !ADDR) break; uint32_t parsedID = 0; if (!parseUint32(*ID, parsedID)) continue; result.emplace_back(SWindowEntry{ .id = parsedID, .windowClass = std::string{*CLASS}, .title = std::string{*TITLE}, }); } return result; } std::vector parseWorkspaceList(std::string_view list) { std::vector result; while (!list.empty()) { const auto LENGTH = takeUntil(list, ":"); uint32_t nameLength = 0; if (!LENGTH || !parseUint32(*LENGTH, nameLength) || nameLength > list.size()) break; const auto NAME = list.substr(0, nameLength); list.remove_prefix(nameLength); if (!list.starts_with(':')) break; list.remove_prefix(1); const auto ID = takeUntil(list, ";"); if (!ID) break; if (ID->empty()) continue; uint64_t id = 0; const auto PARSED = std::from_chars(ID->data(), ID->data() + ID->size(), id); if (PARSED.ec != std::errc{} || PARSED.ptr != ID->data() + ID->size() || id == 0) continue; result.emplace_back(SWorkspaceEntry{ .id = id, .name = std::string{NAME}, }); } return result; } static std::optional takeLengthPrefixed(std::string_view& value) { const auto LENGTH = takeUntil(value, ":"); uint32_t length = 0; if (!LENGTH || !parseUint32(*LENGTH, length) || length > value.size()) return std::nullopt; const auto RESULT = value.substr(0, length); value.remove_prefix(length); return RESULT; } std::vector parseWorkspaceListV3(std::string_view list) { std::vector result; while (!list.empty()) { const auto ID = takeUntil(list, "[HN>]"); if (!ID || ID->empty()) break; uint64_t id = 0; const auto PARSED = std::from_chars(ID->data(), ID->data() + ID->size(), id); if (PARSED.ec != std::errc{} || PARSED.ptr != ID->data() + ID->size() || id == 0) break; const auto NAME = takeLengthPrefixed(list); if (!NAME || !list.starts_with("[HM>]")) break; list.remove_prefix(std::string_view{"[HM>]"}.size()); const auto COUNT = takeUntil(list, ":"); uint32_t outputCount = 0; if (!COUNT || !parseUint32(*COUNT, outputCount)) break; SWorkspaceEntry workspace{ .id = id, .name = std::string{*NAME}, }; for (uint32_t i = 0; i < outputCount; ++i) { const auto OUTPUT = takeLengthPrefixed(list); if (!OUTPUT) return result; workspace.outputs.emplace_back(*OUTPUT); } if (!list.starts_with("[HE>]")) break; list.remove_prefix(std::string_view{"[HE>]"}.size()); result.emplace_back(std::move(workspace)); } return result; } std::string workspaceLabel(const SWorkspaceEntry& workspace) { std::string result = "Workspace " + workspace.name; if (workspace.outputs.empty()) return result; result += workspace.outputs.size() == 1 ? " on monitor " : " on monitors "; for (size_t i = 0; i < workspace.outputs.size(); ++i) { if (i != 0) result += ", "; result += workspace.outputs[i]; } return result; } std::vector parseOutputList(std::string_view list) { std::vector result; while (!list.empty()) { const auto LENGTH_END = list.find(':'); if (LENGTH_END == std::string_view::npos) break; uint32_t nameLength = 0; if (!parseUint32(list.substr(0, LENGTH_END), nameLength)) break; list.remove_prefix(LENGTH_END + 1); if (nameLength > list.size()) break; SOutputEntry output{ .name = std::string{list.substr(0, nameLength)}, .description = "", }; list.remove_prefix(nameLength); if (list.empty() || list.front() != ':') break; list.remove_prefix(1); const auto X = takeUntil(list, ":"); const auto Y = takeUntil(list, ":"); const auto WIDTH = takeUntil(list, ":"); const auto HEIGHT = takeUntil(list, ";"); if (!X || !Y || !WIDTH || !HEIGHT) break; if (!parseInt32(*X, output.x) || !parseInt32(*Y, output.y) || !parseInt32(*WIDTH, output.width) || !parseInt32(*HEIGHT, output.height) || output.width <= 0 || output.height <= 0) continue; output.hasGeometry = true; result.emplace_back(std::move(output)); } return result; } std::optional parseRegion(std::string_view result, const std::vector& outputs) { while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.remove_suffix(1); std::array fields; for (size_t i = 0; i < fields.size() - 1; ++i) { const auto VALUE = takeUntil(result, " "); if (!VALUE || VALUE->empty()) return std::nullopt; fields[i] = *VALUE; } fields.back() = result; if (fields.back().empty() || fields.back().contains(' ')) return std::nullopt; int32_t x = 0, y = 0, width = 0, height = 0; if (!parseInt32(fields[1], x) || !parseInt32(fields[2], y) || !parseInt32(fields[3], width) || !parseInt32(fields[4], height) || x < 0 || y < 0 || width <= 0 || height <= 0) return std::nullopt; const auto OUTPUT = std::ranges::find_if(outputs, [&fields](const auto& output) { return output.name == fields[0]; }); if (OUTPUT == outputs.end()) return std::nullopt; return SSelection{ .type = SELECTION_REGION, .output = OUTPUT->name, .x = x, .y = y, .width = width, .height = height, }; } std::string formatSelection(const SSelection& selection, bool allowToken) { std::string result = std::string{"[SELECTION]"} + (allowToken ? "r/" : "/"); switch (selection.type) { case SELECTION_OUTPUT: return result + "screen:" + selection.output + "\n"; case SELECTION_WINDOW: return result + "window:" + std::to_string(selection.windowID) + "\n"; case SELECTION_WORKSPACE: if (selection.workspaceID == 0) return {}; return result + "workspace-id:" + std::to_string(selection.workspaceID) + "\n"; case SELECTION_REGION: return result + "region:" + selection.output + "@" + std::to_string(selection.x) + "," + std::to_string(selection.y) + "," + std::to_string(selection.width) + "," + std::to_string(selection.height) + "\n"; case SELECTION_NONE: return {}; } return {}; } std::string escapeMarkup(std::string_view text) { std::string result; result.reserve(text.size()); for (const auto CHARACTER : text) { if (static_cast(CHARACTER) < 0x20) { result += ' '; continue; } switch (CHARACTER) { case '&': result += "&"; break; case '<': result += "<"; break; case '>': result += ">"; break; case '\'': result += "'"; break; case '"': result += """; break; default: result += CHARACTER; break; } } return result; } hyprwm-xdg-desktop-portal-hyprland-d1467fe/hyprland-share-picker/PickerData.hpp000066400000000000000000000035261527035313200277520ustar00rootroot00000000000000#pragma once #include #include #include #include #include struct SWindowEntry { uint32_t id = 0; std::string windowClass; std::string title; }; struct SWorkspaceEntry { uint64_t id = 0; std::string name; std::vector outputs; }; struct SOutputEntry { std::string name; std::string description; int32_t x = 0; int32_t y = 0; int32_t width = 0; int32_t height = 0; bool hasGeometry = false; }; enum eSelectionType : uint8_t { SELECTION_NONE = 0, SELECTION_OUTPUT, SELECTION_WINDOW, SELECTION_WORKSPACE, SELECTION_REGION, }; struct SSelection { eSelectionType type = SELECTION_NONE; std::string output; uint32_t windowID = 0; uint64_t workspaceID = 0; int32_t x = 0; int32_t y = 0; int32_t width = 0; int32_t height = 0; }; std::vector parseWindowList(std::string_view list); std::vector parseOutputList(std::string_view list); // Concatenated