diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..90314ef --- /dev/null +++ b/.clang-format @@ -0,0 +1,65 @@ +--- +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 diff --git a/.gitignore b/.gitignore index b89059a..e2d84e4 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,10 @@ dkms.conf build/ build-*/ +.cache .vscode/ -hyprland-share-picker/build/ \ No newline at end of file +hyprland-share-picker/build/ + +protocols/*.c +protocols/*.h \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 33bc81f..e863dde 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [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 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ce56aba --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_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} +) + +set(CMAKE_MESSAGE_LOG_LEVEL "STATUS") + +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() + +include_directories( + . + "protocols/" + "subprojects/sdbus-cpp/include/" +) + +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) + +message(STATUS "Checking deps...") +add_subdirectory(subprojects/sdbus-cpp) +add_subdirectory(hyprland-share-picker) +find_package(Threads REQUIRED) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols cairo pango pangocairo libjpeg libpipewire-0.3 libspa-0.2 libdrm gbm) + +file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") +add_executable(xdg-desktop-portal-hyprland ${SRCFILES}) +target_link_libraries(xdg-desktop-portal-hyprland PRIVATE rt sdbus-c++ PkgConfig::deps) + +# protocols +find_program(WaylandScanner NAMES wayland-scanner) +message(STATUS "Found WaylandScanner at ${WaylandScanner}") +execute_process( + COMMAND pkg-config --variable=pkgdatadir wayland-protocols + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE WAYLAND_PROTOCOLS_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE) +message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") + +function(protocol protoPath protoName external) + if (external) + execute_process( + COMMAND ${WaylandScanner} client-header ${protoPath} protocols/${protoName}-protocol.h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + execute_process( + COMMAND ${WaylandScanner} private-code ${protoPath} protocols/${protoName}-protocol.c + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + target_sources(xdg-desktop-portal-hyprland PRIVATE protocols/${protoName}-protocol.c) + else() + execute_process( + COMMAND ${WaylandScanner} client-header ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + execute_process( + COMMAND ${WaylandScanner} private-code ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.c + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + target_sources(xdg-desktop-portal-hyprland PRIVATE protocols/${protoName}-protocol.c) + endif() +endfunction() + +protocol("protocols/wlr-foreign-toplevel-management-unstable-v1.xml" "wlr-foreign-toplevel-management-unstable-v1" true) +protocol("protocols/wlr-screencopy-unstable-v1.xml" "wlr-screencopy-unstable-v1" true) +protocol("subprojects/hyprland-protocols/protocols/hyprland-global-shortcuts-v1.xml" "hyprland-global-shortcuts-v1" true) +protocol("subprojects/hyprland-protocols/protocols/hyprland-toplevel-export-v1.xml" "hyprland-toplevel-export-v1" true) +protocol("unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml" "linux-dmabuf-unstable-v1" false) +## diff --git a/LICENSE b/LICENSE index 4ee43a4..f18025f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,29 @@ -MIT License +BSD 3-Clause License -Copyright (c) 2018 emersion +Copyright (c) 2023, vaxerski +All rights reserved. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +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. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3d76318 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +PREFIX = /usr/local +LIBEXEC = /usr/lib +SHARE = /usr/share + +all: + $(MAKE) release + +release: + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja + cmake --build ./build --config Release --target all -j$(nproc) + +debug: + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -H./ -B./build -G Ninja + cmake --build ./build --config Debug --target all -j$(nproc) + +install: + $(MAKE) release + cp ./build/hyprland-share-picker/hyprland-share-picker ${PREFIX}/bin + cp ./build/xdg-desktop-portal-hyprland ${LIBEXEC}/ + cp ./hyprland.portal ${SHARE}/xdg-desktop-portal/portals/ + cp ./org.freedesktop.impl.portal.desktop.hyprland.service ${SHARE}/dbus-1/services/ \ No newline at end of file diff --git a/README.md b/README.md index d0d6c79..5f8c5b9 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,16 @@ # xdg-desktop-portal-hyprland - -[xdg-desktop-portal] backend for hyprland - -## What and why? -Due to reasons explained in [hyprland-protocols](https://github.com/hyprwm/hyprland-protocols), -we have a separate desktop portal impl for Hyprland. - -Although `-wlr` **does** work with Hyprland, `-hyprland` offers more features. - -## Additional dependencies -XDPH depends on `qt6` and `qt6-wayland` for the sharing selector. Lack of either will -cause screensharing to not work at all. +An [XDG Desktop Portal](https://github.com/flatpak/xdg-desktop-portal) backend for Hyprland. ## Building - ```sh -meson build --prefix=/usr -ninja -C build -cd hyprland-share-picker && make all && cd .. +make all ``` ## Installing - -### From Source - ```sh -ninja -C build install -sudo cp ./hyprland-share-picker/build/hyprland-share-picker /usr/bin +sudo make install ``` -### AUR -```sh -yay -S xdg-desktop-portal-hyprland-git -``` +## Running, FAQs, etc. +See [the Hyprland wiki](https://wiki.hyprland.org/Useful-Utilities/Hyprland-desktop-portal/) -## Usage - -Although should start automatically, consult [the Hyprland wiki](https://wiki.hyprland.org/Useful-Utilities/Hyprland-desktop-portal/) -in case of issues. - -## For other wlroots-based compositors -If you are a developer and wish to support features that XDPH provides, make sure to support those protocols: - - `wlr_foreign_toplevel_management_unstable_v1` - - `hyprland_toplevel_export_v1` - XDPH uses Rev2 exclusively (`_with_toplevel_handle`) - - -## License -MIT - -[xdg-desktop-portal]: https://github.com/flatpak/xdg-desktop-portal -[screencast compatibility]: https://github.com/emersion/xdg-desktop-portal-wlr/wiki/Screencast-Compatibility diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/flake.lock b/flake.lock index adc6ef7..6e6f314 100644 --- a/flake.lock +++ b/flake.lock @@ -25,11 +25,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1683014792, - "narHash": "sha256-6Va9iVtmmsw4raBc3QKvQT2KT/NGRWlvUlJj46zN8B8=", + "lastModified": 1693844670, + "narHash": "sha256-t69F2nBB8DNQUWHD809oJZJVE+23XBrth4QZuVd6IE0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1a411f23ba299db155a5b45d5e145b85a7aafc42", + "rev": "3c15feef7770eb5500a4b8792623e2d6f598c9c1", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index c94fcd1..d8baf14 100644 --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,7 @@ inputs.hyprland-protocols.overlays.default self.overlays.xdg-desktop-portal-hyprland self.overlays.hyprland-share-picker + self.overlays.package-overrides ]; }); in { diff --git a/hyprland-share-picker/main.cpp b/hyprland-share-picker/main.cpp index b354641..9deeaca 100644 --- a/hyprland-share-picker/main.cpp +++ b/hyprland-share-picker/main.cpp @@ -18,8 +18,8 @@ #include "mainpicker.h" std::string execAndGet(const char* cmd) { - std::array buffer; - std::string result; + std::array buffer; + std::string result; std::unique_ptr pipe(popen(cmd, "r"), pclose); if (!pipe) { throw std::runtime_error("popen() failed!"); @@ -30,12 +30,12 @@ std::string execAndGet(const char* cmd) { return result; } -QApplication* pickerPtr = nullptr; -MainPicker* mainPickerPtr = nullptr; +QApplication* pickerPtr = nullptr; +MainPicker* mainPickerPtr = nullptr; struct SWindowEntry { - std::string name; - std::string clazz; + std::string name; + std::string clazz; unsigned long long id = 0; }; @@ -50,15 +50,15 @@ std::vector getWindows(const char* env) { while (!rolling.empty()) { // ID const auto IDSEPPOS = rolling.find("[HC>]"); - const auto IDSTR = rolling.substr(0, IDSEPPOS); + const auto IDSTR = rolling.substr(0, IDSEPPOS); // class const auto CLASSSEPPOS = rolling.find("[HT>]"); - const auto CLASSSTR = rolling.substr(IDSEPPOS + 5, CLASSSEPPOS - IDSEPPOS - 5); + const auto CLASSSTR = rolling.substr(IDSEPPOS + 5, CLASSSEPPOS - IDSEPPOS - 5); // title const auto TITLESEPPOS = rolling.find("[HE>]"); - const auto TITLESTR = rolling.substr(CLASSSEPPOS + 5, TITLESEPPOS - 5 - CLASSSEPPOS); + const auto TITLESTR = rolling.substr(CLASSSEPPOS + 5, TITLESEPPOS - 5 - CLASSSEPPOS); try { result.push_back({TITLESTR, CLASSSTR, std::stoull(IDSTR)}); @@ -75,9 +75,9 @@ std::vector getWindows(const char* env) { int main(int argc, char* argv[]) { qputenv("QT_LOGGING_RULES", "qml=false"); - const char* WINDOWLISTSTR = getenv("XDPH_WINDOW_SHARING_LIST"); + const char* WINDOWLISTSTR = getenv("XDPH_WINDOW_SHARING_LIST"); - const auto WINDOWLIST = getWindows(WINDOWLISTSTR); + const auto WINDOWLIST = getWindows(WINDOWLISTSTR); QApplication picker(argc, argv); pickerPtr = &picker; @@ -85,30 +85,37 @@ int main(int argc, char* argv[]) { mainPickerPtr = &w; // get the tabwidget - const auto TABWIDGET = (QTabWidget*)w.children()[1]->children()[0]; + const auto TABWIDGET = (QTabWidget*)w.children()[1]->children()[0]; + const auto ALLOWTOKENBUTTON = (QCheckBox*)w.children()[1]->children()[1]; const auto TAB1 = (QWidget*)TABWIDGET->children()[0]; - const auto SCREENS_SCROLL_AREA_CONTENTS = (QWidget*)TAB1->findChild("screens")->findChild("scrollArea")->findChild("scrollAreaWidgetContents"); + const auto SCREENS_SCROLL_AREA_CONTENTS = + (QWidget*)TAB1->findChild("screens")->findChild("scrollArea")->findChild("scrollAreaWidgetContents"); // add all screens - const auto SCREENS = picker.screens(); + const auto SCREENS = picker.screens(); - constexpr int BUTTON_WIDTH = 441; + constexpr int BUTTON_WIDTH = 441; constexpr int BUTTON_HEIGHT = 41; - constexpr int BUTTON_PAD = 4; + constexpr int BUTTON_PAD = 4; for (int i = 0; i < SCREENS.size(); ++i) { - const auto GEOMETRY = SCREENS[i]->geometry(); + const auto GEOMETRY = SCREENS[i]->geometry(); - QString text = QString::fromStdString(std::string("Screen " + std::to_string(i) + " at " + std::to_string(GEOMETRY.x()) + ", " + std::to_string(GEOMETRY.y()) + " (" + std::to_string(GEOMETRY.width()) + "x" + std::to_string(GEOMETRY.height()) + ") (") + SCREENS[i]->name().toStdString() + ")"); + QString text = QString::fromStdString(std::string("Screen " + std::to_string(i) + " at " + std::to_string(GEOMETRY.x()) + ", " + std::to_string(GEOMETRY.y()) + " (" + + std::to_string(GEOMETRY.width()) + "x" + std::to_string(GEOMETRY.height()) + ") (") + + SCREENS[i]->name().toStdString() + ")"); QPushButton* button = new QPushButton(text, (QWidget*)SCREENS_SCROLL_AREA_CONTENTS); button->move(9, 5 + (BUTTON_HEIGHT + BUTTON_PAD) * i); button->resize(BUTTON_WIDTH, BUTTON_HEIGHT); QObject::connect(button, &QPushButton::clicked, [=]() { std::string ID = button->text().toStdString(); - ID = ID.substr(ID.find_last_of('(') + 1); - ID = ID.substr(0, ID.find_last_of(')')); + ID = ID.substr(ID.find_last_of('(') + 1); + ID = ID.substr(0, ID.find_last_of(')')); + + std::cout << (ALLOWTOKENBUTTON->isChecked() ? "r" : ""); + std::cout << "/"; std::cout << "screen:" << ID << "\n"; pickerPtr->quit(); @@ -119,12 +126,13 @@ int main(int argc, char* argv[]) { SCREENS_SCROLL_AREA_CONTENTS->resize(SCREENS_SCROLL_AREA_CONTENTS->size().width(), 5 + (BUTTON_HEIGHT + BUTTON_PAD) * SCREENS.size()); // windows - const auto WINDOWS_SCROLL_AREA_CONTENTS = (QWidget*)TAB1->findChild("windows")->findChild("scrollArea_2")->findChild("scrollAreaWidgetContents_2"); + const auto WINDOWS_SCROLL_AREA_CONTENTS = + (QWidget*)TAB1->findChild("windows")->findChild("scrollArea_2")->findChild("scrollAreaWidgetContents_2"); // loop over them int windowIterator = 0; for (auto& window : WINDOWLIST) { - QString text = QString::fromStdString(window.clazz + ": " + window.name); + QString text = QString::fromStdString(window.clazz + ": " + window.name); QPushButton* button = new QPushButton(text, (QWidget*)WINDOWS_SCROLL_AREA_CONTENTS); button->move(9, 5 + (BUTTON_HEIGHT + BUTTON_PAD) * windowIterator); @@ -133,6 +141,9 @@ int main(int argc, char* argv[]) { mainPickerPtr->windowIDs[button] = window.id; QObject::connect(button, &QPushButton::clicked, [=]() { + std::cout << (ALLOWTOKENBUTTON->isChecked() ? "r" : ""); + std::cout << "/"; + std::cout << "window:" << mainPickerPtr->windowIDs[button] << "\n"; pickerPtr->quit(); return 0; @@ -144,16 +155,16 @@ int main(int argc, char* argv[]) { WINDOWS_SCROLL_AREA_CONTENTS->resize(WINDOWS_SCROLL_AREA_CONTENTS->size().width(), 5 + (BUTTON_HEIGHT + BUTTON_PAD) * windowIterator); // lastly, region - const auto REGION_OBJECT = (QWidget*)TAB1->findChild("region"); + const auto REGION_OBJECT = (QWidget*)TAB1->findChild("region"); - QString text = "Select region..."; + QString text = "Select region..."; QPushButton* button = new QPushButton(text, (QWidget*)REGION_OBJECT); button->move(79, 80); button->resize(321, 41); QObject::connect(button, &QPushButton::clicked, [=]() { auto REGION = execAndGet("slurp -f \"%o %x %y %w %h\""); - REGION = REGION.substr(0, REGION.length()); + REGION = REGION.substr(0, REGION.length()); // now, get the screen QScreen* pScreen = nullptr; @@ -179,15 +190,18 @@ int main(int argc, char* argv[]) { // get all the coords try { - REGION = REGION.substr(REGION.find_first_of(' ') + 1); + REGION = REGION.substr(REGION.find_first_of(' ') + 1); const auto X = std::stoi(REGION.substr(0, REGION.find_first_of(' '))); - REGION = REGION.substr(REGION.find_first_of(' ') + 1); + REGION = REGION.substr(REGION.find_first_of(' ') + 1); const auto Y = std::stoi(REGION.substr(0, REGION.find_first_of(' '))); - REGION = REGION.substr(REGION.find_first_of(' ') + 1); + REGION = REGION.substr(REGION.find_first_of(' ') + 1); const auto W = std::stoi(REGION.substr(0, REGION.find_first_of(' '))); - REGION = REGION.substr(REGION.find_first_of(' ') + 1); + REGION = REGION.substr(REGION.find_first_of(' ') + 1); const auto H = std::stoi(REGION); + std::cout << (ALLOWTOKENBUTTON->isChecked() ? "r" : ""); + std::cout << "/"; + std::cout << "region:" << SCREEN_NAME << "@" << X - pScreen->geometry().x() << "," << Y - pScreen->geometry().y() << "," << W << "," << H << "\n"; pickerPtr->quit(); return 0; diff --git a/hyprland-share-picker/mainpicker.ui b/hyprland-share-picker/mainpicker.ui index c40a385..c14d1d6 100644 --- a/hyprland-share-picker/mainpicker.ui +++ b/hyprland-share-picker/mainpicker.ui @@ -66,7 +66,7 @@ QTabWidget::North - 0 + 1 @@ -78,9 +78,12 @@ 9 9 461 - 241 + 201 + + Qt::ScrollBarAlwaysOff + false @@ -115,9 +118,12 @@ 9 9 461 - 241 + 201 + + Qt::ScrollBarAlwaysOff + false @@ -148,6 +154,22 @@ + + + + 340 + 256 + 140 + 21 + + + + By selecting this, the application will be given a restore token that it can use to skip prompting you next time. Only select if you trust the application. + + + Allow a restore token + + diff --git a/include/config.h b/include/config.h deleted file mode 100644 index f856dc1..0000000 --- a/include/config.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -#include "logger.h" -#include "screencast_common.h" - -struct config_screencast { - char *output_name; - double max_fps; - char *exec_before; - char *exec_after; - char *chooser_cmd; - enum xdpw_chooser_types chooser_type; - bool force_mod_linear; -}; - -struct xdpw_config { - struct config_screencast screencast_conf; -}; - -void print_config(enum LOGLEVEL loglevel, struct xdpw_config *config); -void finish_config(struct xdpw_config *config); -void init_config(char ** const configfile, struct xdpw_config *config); - -#endif diff --git a/include/fps_limit.h b/include/fps_limit.h deleted file mode 100644 index 9312745..0000000 --- a/include/fps_limit.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef FPS_LIMIT_H -#define FPS_LIMIT_H - -#include -#include - -struct fps_limit_state { - struct timespec frame_last_time; - - struct timespec fps_last_time; - uint64_t fps_frame_count; -}; - -void fps_limit_measure_start(struct fps_limit_state *state, double max_fps); - -uint64_t fps_limit_measure_end(struct fps_limit_state *state, double max_fps); - -#endif diff --git a/include/global_shortcuts.h b/include/global_shortcuts.h deleted file mode 100644 index 4b8f1f7..0000000 --- a/include/global_shortcuts.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include - -#include "protocols/hyprland-global-shortcuts-v1-client-protocol.h" - -struct xdpw_state; -struct xdpw_session; - -struct globalShortcut { - char* name; - char* description; - struct wl_list link; - struct hyprland_global_shortcut_v1* hlShortcut; - bool bound; -}; - -struct globalShortcutsClient { - struct xdpw_session* session; - struct wl_list shortcuts; // struct globalShortcut* - struct wl_list link; - char* parent_window; - bool sentShortcuts; -}; - -struct globalShortcutsInstance { - struct hyprland_global_shortcuts_manager_v1* manager; - struct wl_list shortcutClients; // struct globalShortcutsClient* -}; - -void initShortcutsInstance(struct xdpw_state* state, struct globalShortcutsInstance* instance); \ No newline at end of file diff --git a/include/logger.h b/include/logger.h deleted file mode 100644 index 89a2cb3..0000000 --- a/include/logger.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef LOGGER_H -#define LOGGER_H - -#include - -#define DEFAULT_LOGLEVEL ERROR - -enum LOGLEVEL { QUIET, ERROR, WARN, INFO, DEBUG, TRACE }; - -struct logger_properties { - enum LOGLEVEL level; - FILE *dst; -}; - -void init_logger(FILE *dst, enum LOGLEVEL level); -enum LOGLEVEL get_loglevel(const char *level); -void logprint(enum LOGLEVEL level, char *msg, ...); - -#endif diff --git a/include/pipewire_screencast.h b/include/pipewire_screencast.h deleted file mode 100644 index 64ae141..0000000 --- a/include/pipewire_screencast.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef PIPEWIRE_SCREENCAST_H -#define PIPEWIRE_SCREENCAST_H - -#include "screencast_common.h" - -#define XDPW_PWR_BUFFERS 2 -#define XDPW_PWR_BUFFERS_MIN 2 -#define XDPW_PWR_ALIGN 16 - -void xdpw_pwr_dequeue_buffer(struct xdpw_screencast_instance *cast); -void xdpw_pwr_enqueue_buffer(struct xdpw_screencast_instance *cast); -void pwr_update_stream_param(struct xdpw_screencast_instance *cast); -void xdpw_pwr_stream_create(struct xdpw_screencast_instance *cast); -void xdpw_pwr_stream_destroy(struct xdpw_screencast_instance *cast); -int xdpw_pwr_context_create(struct xdpw_state *state); -void xdpw_pwr_context_destroy(struct xdpw_state *state); - -#endif diff --git a/include/screencast.h b/include/screencast.h deleted file mode 100644 index 99b7411..0000000 --- a/include/screencast.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef SCREENCAST_H -#define SCREENCAST_H - -#include "screencast_common.h" - -void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast); -void xdpw_screencast_instance_teardown(struct xdpw_screencast_instance *cast); - -#endif diff --git a/include/screencast_common.h b/include/screencast_common.h deleted file mode 100644 index 9f32e42..0000000 --- a/include/screencast_common.h +++ /dev/null @@ -1,238 +0,0 @@ -#ifndef SCREENCAST_COMMON_H -#define SCREENCAST_COMMON_H - -#include -#include -#include -#include -#include -#include - -#include "fps_limit.h" -#include "hyprland-toplevel-export-v1-client-protocol.h" -#include "utils.h" - -// this seems to be right based on -// https://github.com/flatpak/xdg-desktop-portal/blob/309a1fc0cf2fb32cceb91dbc666d20cf0a3202c2/src/screen-cast.c#L955 -#define XDP_CAST_PROTO_VER 3 - -enum cursor_modes { - HIDDEN = 1, - EMBEDDED = 2, - METADATA = 4, -}; - -enum source_types { - MONITOR = 1, - WINDOW = 2, -}; - -enum buffer_type { - WL_SHM = 0, - DMABUF = 1, -}; - -enum xdpw_chooser_types { - XDPW_CHOOSER_DEFAULT, - XDPW_CHOOSER_NONE, - XDPW_CHOOSER_SIMPLE, - XDPW_CHOOSER_DMENU, -}; - -enum xdpw_frame_state { - XDPW_FRAME_STATE_NONE, - XDPW_FRAME_STATE_STARTED, - XDPW_FRAME_STATE_RENEG, - XDPW_FRAME_STATE_FAILED, - XDPW_FRAME_STATE_SUCCESS, -}; - -struct xdpw_output_chooser { - enum xdpw_chooser_types type; - char *cmd; -}; - -struct xdpw_frame_damage { - uint32_t x; - uint32_t y; - uint32_t width; - uint32_t height; -}; - -struct xdpw_frame { - bool y_invert; - uint64_t tv_sec; - uint32_t tv_nsec; - struct xdpw_frame_damage damage[4]; - uint32_t damage_count; - struct xdpw_buffer *xdpw_buffer; - struct pw_buffer *pw_buffer; -}; - -struct xdpw_screencopy_frame_info { - uint32_t width; - uint32_t height; - uint32_t size; - uint32_t stride; - uint32_t format; -}; - -struct xdpw_buffer { - struct wl_list link; - enum buffer_type buffer_type; - - uint32_t width; - uint32_t height; - uint32_t format; - int plane_count; - - int fd[4]; - uint32_t size[4]; - uint32_t stride[4]; - uint32_t offset[4]; - - struct gbm_bo *bo; - - struct wl_buffer *buffer; -}; - -struct xdpw_format_modifier_pair { - uint32_t fourcc; - uint64_t modifier; -}; - -struct xdpw_dmabuf_feedback_data { - void *format_table_data; - uint32_t format_table_size; - bool device_used; -}; - -struct xdpw_screencast_context { - // xdpw - struct xdpw_state *state; - - // pipewire - struct pw_context *pwr_context; - struct pw_core *core; - - // wlroots - struct wl_list output_list; - struct wl_registry *registry; - struct zwlr_screencopy_manager_v1 *screencopy_manager; - struct zxdg_output_manager_v1 *xdg_output_manager; - struct wl_shm *shm; - struct zwp_linux_dmabuf_v1 *linux_dmabuf; - struct zwp_linux_dmabuf_feedback_v1 *linux_dmabuf_feedback; - struct xdpw_dmabuf_feedback_data feedback_data; - struct wl_array format_modifier_pairs; - - // hyprland - struct hyprland_toplevel_export_manager_v1 *hyprland_toplevel_manager; - struct zwlr_foreign_toplevel_manager_v1 *wlroots_toplevel_manager; - struct wl_list toplevel_resource_list; - - // gbm - struct gbm_device *gbm; - - // sessions - struct wl_list screencast_instances; -}; - -struct xdpw_wlr_output { - struct wl_list link; - uint32_t id; - struct wl_output *output; - struct zxdg_output_v1 *xdg_output; - char *make; - char *model; - char *name; - int width; - int height; - float framerate; - enum wl_output_transform transform; -}; - -struct xdpw_share { - struct xdpw_wlr_output *output; - int x; - int y; - int w; - int h; - int window_handle; -}; - -struct xdph_restore_token { - char *token; - char *outputPort; // NULL if not set - char *windowClass; // NULL if not set - uint64_t windowHandle; // 0 if not set - struct wl_list link; - bool withCursor; -}; - -struct xdpw_screencast_instance { - // list - struct wl_list link; - - // xdpw - uint32_t refcount; - struct xdpw_screencast_context *ctx; - bool initialized; - struct xdpw_frame current_frame; - enum xdpw_frame_state frame_state; - struct wl_list buffer_list; - bool avoid_dmabufs; - - // pipewire - struct pw_stream *stream; - struct spa_hook stream_listener; - struct spa_video_info_raw pwr_format; - uint32_t seq; - uint32_t node_id; - bool pwr_stream_state; - uint32_t framerate; - - // wlroots - struct zwlr_screencopy_frame_v1 *frame_callback; - struct xdpw_share target; - uint32_t max_framerate; - struct zwlr_screencopy_frame_v1 *wlr_frame; - struct xdpw_screencopy_frame_info screencopy_frame_info[2]; - bool with_cursor; - int err; - bool quit; - bool teardown; - enum buffer_type buffer_type; - - // hyprland - struct hyprland_toplevel_export_frame_v1 *frame_callback_hyprland; - struct hyprland_toplevel_export_frame_v1 *hyprland_frame; - - // fps limit - struct fps_limit_state fps_limit; -}; - -struct SToplevelEntry { - struct zwlr_foreign_toplevel_handle_v1 *handle; - char name[256]; - char clazz[256]; - struct wl_list link; -}; - -void randname(char *buf); -struct gbm_device *xdpw_gbm_device_create(drmDevice *device); -struct xdpw_buffer *xdpw_buffer_create(struct xdpw_screencast_instance *cast, enum buffer_type buffer_type, - struct xdpw_screencopy_frame_info *frame_info); -void xdpw_buffer_destroy(struct xdpw_buffer *buffer); -bool wlr_query_dmabuf_modifiers(struct xdpw_screencast_context *ctx, uint32_t drm_format, uint32_t num_modifiers, uint64_t *modifiers, - uint32_t *max_modifiers); -enum wl_shm_format xdpw_format_wl_shm_from_drm_fourcc(uint32_t format); -uint32_t xdpw_format_drm_fourcc_from_wl_shm(enum wl_shm_format format); -enum spa_video_format xdpw_format_pw_from_drm_fourcc(uint32_t format); -enum spa_video_format xdpw_format_pw_strip_alpha(enum spa_video_format format); - -struct xdpw_frame_damage merge_damage(struct xdpw_frame_damage *damage1, struct xdpw_frame_damage *damage2); - -enum xdpw_chooser_types get_chooser_type(const char *chooser_type); -const char *chooser_type_str(enum xdpw_chooser_types chooser_type); -#endif /* SCREENCAST_COMMON_H */ diff --git a/include/screenshot.h b/include/screenshot.h deleted file mode 100644 index c3d8e33..0000000 --- a/include/screenshot.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef SCREENSHOT_H -#define SCREENSHOT_H - - -struct xdpw_ppm_pixel { - int max_color_value; - unsigned char red, green, blue; -}; - -#endif \ No newline at end of file diff --git a/include/screenshot_common.h b/include/screenshot_common.h deleted file mode 100644 index e02863a..0000000 --- a/include/screenshot_common.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef SCREENSHOT_COMMON_H -#define SCREENSHOT_COMMON_H - -#define XDP_SHOT_PROTO_VER 2 - -#endif diff --git a/include/timespec_util.h b/include/timespec_util.h deleted file mode 100644 index 2ab27b8..0000000 --- a/include/timespec_util.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef TIMESPEC_UTIL_H -#define TIMESPEC_UTIL_H - -#include -#include -#include - -#define TIMESPEC_NSEC_PER_SEC 1000000000L - -void timespec_add(struct timespec *t, int64_t delta_ns); - -bool timespec_less(struct timespec *t1, struct timespec *t2); - -bool timespec_is_zero(struct timespec *t); - -int64_t timespec_diff_ns(struct timespec *t1, struct timespec *t2); - -#endif diff --git a/include/utils.h b/include/utils.h deleted file mode 100644 index a8110c6..0000000 --- a/include/utils.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include -#include - -char *getFormat(const char *fmt, ...); \ No newline at end of file diff --git a/include/wlr_screencast.h b/include/wlr_screencast.h deleted file mode 100644 index efd7269..0000000 --- a/include/wlr_screencast.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef WLR_SCREENCAST_H -#define WLR_SCREENCAST_H - -#include "screencast_common.h" - -#define WL_OUTPUT_VERSION 1 - -#define SC_MANAGER_VERSION 3 -#define SC_MANAGER_VERSION_MIN 2 - -#define WL_SHM_VERSION 1 - -#define XDG_OUTPUT_MANAGER_VERSION 3 - -#define LINUX_DMABUF_VERSION 4 -#define LINUX_DMABUF_VERSION_MIN 3 - -struct xdpw_state; - -int xdpw_wlr_screencopy_init(struct xdpw_state *state); -void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx); - -struct xdpw_wlr_output *xdpw_wlr_output_find_by_name(struct wl_list *output_list, - const char *name); -struct xdpw_wlr_output *xdpw_wlr_output_first(struct wl_list *output_list); -struct xdpw_wlr_output *xdpw_wlr_output_find(struct xdpw_screencast_context *ctx, - struct wl_output *out, uint32_t id); -struct xdpw_share xdpw_wlr_chooser(struct xdpw_screencast_context *ctx); - -void xdpw_wlr_frame_finish(struct xdpw_screencast_instance *cast); -void xdpw_wlr_frame_start(struct xdpw_screencast_instance *cast); -void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast); - -#endif diff --git a/include/xdpw.h b/include/xdpw.h deleted file mode 100644 index b82fb85..0000000 --- a/include/xdpw.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef XDPW_H -#define XDPW_H - -#include -#ifdef HAVE_LIBSYSTEMD -#include -#elif HAVE_LIBELOGIND -#include -#elif HAVE_BASU -#include -#endif - -#include "config.h" -#include "global_shortcuts.h" -#include "screencast_common.h" -#include "screenshot_common.h" - -struct xdpw_state { - struct wl_list xdpw_sessions; - sd_bus *bus; - struct wl_display *wl_display; - struct pw_loop *pw_loop; - struct xdpw_screencast_context screencast; - uint32_t screencast_source_types; // bitfield of enum source_types - uint32_t screencast_cursor_modes; // bitfield of enum cursor_modes - uint32_t screencast_version; - uint32_t screenshot_version; - struct xdpw_config *config; - int timer_poll_fd; - struct wl_list timers; - struct xdpw_timer *next_timer; - struct globalShortcutsInstance shortcutsInstance; - - // saved instances of screencast - // TODO: persist in storage - uint64_t lastRestoreToken; - struct wl_list restore_tokens; // xdph_restore_token -}; - -struct xdpw_request { - sd_bus_slot *slot; -}; - -struct xdpw_session { - struct wl_list link; - sd_bus_slot *slot; - char *session_handle; - char *app_id; - struct xdpw_screencast_instance *screencast_instance; - bool persist; -}; - -typedef void (*xdpw_event_loop_timer_func_t)(void *data); - -struct xdpw_timer { - struct xdpw_state *state; - xdpw_event_loop_timer_func_t func; - void *user_data; - struct timespec at; - struct wl_list link; // xdpw_state::timers -}; - -enum { PORTAL_RESPONSE_SUCCESS = 0, PORTAL_RESPONSE_CANCELLED = 1, PORTAL_RESPONSE_ENDED = 2 }; - -int xdpw_screenshot_init(struct xdpw_state *state); -int xdpw_screencast_init(struct xdpw_state *state); - -struct xdpw_request *xdpw_request_create(sd_bus *bus, const char *object_path); -void xdpw_request_destroy(struct xdpw_request *req); - -struct xdpw_session *xdpw_session_create(struct xdpw_state *state, sd_bus *bus, char *object_path); -void xdpw_session_destroy(struct xdpw_session *req); - -struct xdpw_timer *xdpw_add_timer(struct xdpw_state *state, uint64_t delay_ns, xdpw_event_loop_timer_func_t func, void *data); - -void xdpw_destroy_timer(struct xdpw_timer *timer); - -#endif diff --git a/meson.build b/meson.build index 147c97f..0324a4f 100644 --- a/meson.build +++ b/meson.build @@ -1,111 +1,38 @@ -project( - 'xdg-desktop-portal-hyprland', - 'c', - version: '0.5.0', - license: 'MIT', - meson_version: '>=0.58.0', - default_options: ['c_std=c11', 'warning_level=2', 'werror=false'], -) - -cc = meson.get_compiler('c') - -add_project_arguments(cc.get_supported_arguments([ - '-Wno-missing-braces', - '-Wno-missing-field-initializers', - '-Wno-unused-parameter', - '-D_POSIX_C_SOURCE=200809L', - '-D_GNU_SOURCE', -]), language: 'c') - -prefix = get_option('prefix') -sysconfdir = get_option('sysconfdir') -add_project_arguments('-DSYSCONFDIR="@0@"'.format(join_paths(prefix, sysconfdir)), language : 'c') - -inc = include_directories('include') - -rt = cc.find_library('rt') -pipewire = dependency('libpipewire-0.3', version: '>= 0.3.62') -hyprland_protos = dependency('hyprland-protocols', version: '>=0.2', fallback: 'hyprland-protocols') -wayland_client = dependency('wayland-client') -wayland_protos = dependency('wayland-protocols', version: '>=1.24') -iniparser = dependency('inih') -gbm = dependency('gbm') -drm = dependency('libdrm') -uuid = dependency('uuid') - -epoll = dependency('', required: false) -if (not cc.has_function('timerfd_create', prefix: '#include ') or - not cc.has_function('signalfd', prefix: '#include ')) - epoll = dependency('epoll-shim') -endif - -if get_option('sd-bus-provider') == 'auto' - assert(get_option('auto_features').auto(), 'sd-bus-provider must not be set to auto since auto_features != auto') - sdbus = dependency('libsystemd', - required: false, - not_found_message: 'libsystemd not found, trying libelogind', - ) - if not sdbus.found() - sdbus = dependency('libelogind', - required: false, - not_found_message: 'libelogind not found, trying basu', - ) - endif - if not sdbus.found() - sdbus = dependency('basu', - required: false, - ) - endif - if not sdbus.found() - error('Neither libsystemd, nor libelogind, nor basu was found') - endif -else - sdbus = dependency(get_option('sd-bus-provider')) -endif -add_project_arguments('-DHAVE_' + sdbus.name().to_upper() + '=1', language: 'c') - -subdir('protocols') - -xdpw_files = files([ - 'src/core/main.c', - 'src/core/logger.c', - 'src/core/config.c', - 'src/core/request.c', - 'src/core/session.c', - 'src/core/timer.c', - 'src/core/timespec_util.c', - 'src/core/utils.c', - 'src/screenshot/screenshot.c', - 'src/screencast/screencast.c', - 'src/screencast/screencast_common.c', - 'src/screencast/wlr_screencast.c', - 'src/screencast/pipewire_screencast.c', - 'src/screencast/fps_limit.c', - 'src/globalshortcuts/global_shortcuts.c' -]) - -executable( - 'xdg-desktop-portal-hyprland', - [xdpw_files, wl_proto_files], - dependencies: [ - wayland_client, - sdbus, - pipewire, - rt, - iniparser, - gbm, - drm, - epoll, - uuid, +project('xdg-desktop-portal-hyprland', 'cpp', 'c', + version: run_command('cat', files('VERSION'), check: true).stdout().strip(), + license: 'BSD-3-Clause', + meson_version: '>=0.63.0', + default_options: [ + 'warning_level=2', + 'optimization=3', + 'buildtype=release', + 'debug=false', + # 'cpp_std=c++23' # not yet supported by meson, as of version 0.63.0 ], - include_directories: [inc], - install: true, - install_dir: get_option('libexecdir'), ) +# clang v14.0.6 uses C++2b instead of C++23, so we've gotta account for that +# replace the following with a project default option once meson gets support for C++23 +cpp_compiler = meson.get_compiler('cpp') +if cpp_compiler.has_argument('-std=c++23') + add_global_arguments('-std=c++23', language: 'cpp') +elif cpp_compiler.has_argument('-std=c++2b') + add_global_arguments('-std=c++2b', language: 'cpp') +else + error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') +endif + +add_project_arguments(cpp_compiler.get_supported_arguments([ + '-Wno-missing-field-initializers', + '-Wno-narrowing', + '-Wno-pointer-arith', + '-Wno-unused-parameter', + '-Wno-unused-value', + '-fpermissive' +]), language: 'cpp') + conf_data = configuration_data() -conf_data.set('libexecdir', - join_paths(get_option('prefix'), get_option('libexecdir'))) +conf_data.set('libexecdir', join_paths(get_option('prefix'), get_option('libexecdir'))) conf_data.set('systemd_service', '') systemd = dependency('systemd', required: get_option('systemd')) @@ -136,3 +63,7 @@ install_data( install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal', 'portals'), ) +inc = include_directories('.', 'protocols') + +subdir('protocols') +subdir('src') diff --git a/meson_options.txt b/meson_options.txt index d7ae3ab..5303abc 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,2 +1 @@ -option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library') option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit') diff --git a/nix/default.nix b/nix/default.nix index 838d46f..0e0cc68 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,18 +5,20 @@ meson, ninja, pkg-config, + cairo, + hyprland-share-picker, + libdrm, + libjpeg, + mesa, + pango, + pipewire, + sdbus-cpp, + systemd, wayland-protocols, wayland-scanner, - hyprland-share-picker, grim, slurp, hyprland-protocols, - inih, - libdrm, - libuuid, - mesa, - pipewire, - systemd, wayland, version ? "git", }: @@ -26,8 +28,6 @@ stdenv.mkDerivation { src = ../.; - strictDeps = true; - depsBuildBuild = [pkg-config]; nativeBuildInputs = [ meson ninja @@ -35,22 +35,21 @@ stdenv.mkDerivation { wayland-scanner makeWrapper ]; + buildInputs = [ + cairo hyprland-protocols - inih libdrm - libuuid + libjpeg mesa + pango pipewire + sdbus-cpp systemd wayland wayland-protocols ]; - mesonFlags = [ - "-Dsd-bus-provider=libsystemd" - ]; - postInstall = '' wrapProgram $out/libexec/xdg-desktop-portal-hyprland --prefix PATH ":" ${lib.makeBinPath [hyprland-share-picker grim slurp]} ''; @@ -58,8 +57,8 @@ stdenv.mkDerivation { meta = with lib; { homepage = "https://github.com/hyprwm/xdg-desktop-portal-hyprland"; description = "xdg-desktop-portal backend for Hyprland"; + license = licenses.bsd3; maintainers = with maintainers; [fufexan]; platforms = platforms.linux; - license = licenses.mit; }; } diff --git a/nix/hyprland-share-picker.nix b/nix/hyprland-share-picker.nix index 6632beb..73167b1 100644 --- a/nix/hyprland-share-picker.nix +++ b/nix/hyprland-share-picker.nix @@ -3,12 +3,12 @@ lib, cmake, qtbase, + qtwayland, makeShellWrapper, wrapQtAppsHook, hyprland, slurp, version ? "git", - ... }: stdenv.mkDerivation { pname = "hyprland-share-picker"; @@ -22,6 +22,7 @@ stdenv.mkDerivation { ]; buildInputs = [ qtbase + qtwayland ]; dontWrapQtApps = true; diff --git a/nix/overlays.nix b/nix/overlays.nix index 1688e5d..de3f5b5 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -3,26 +3,46 @@ inputs, lib, }: let + ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION); + mkJoinedOverlays = overlays: final: prev: lib.foldl' (attrs: overlay: attrs // (overlay final prev)) {} overlays; + mkDate = longDate: (lib.concatStringsSep "-" [ (builtins.substring 0 4 longDate) (builtins.substring 4 2 longDate) (builtins.substring 6 2 longDate) ]); - version = "0.pre" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); + + version = ver + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); in { default = mkJoinedOverlays (with self.overlays; [ xdg-desktop-portal-hyprland hyprland-share-picker + package-overrides ]); xdg-desktop-portal-hyprland = final: prev: { xdg-desktop-portal-hyprland = final.callPackage ./default.nix { + stdenv = prev.gcc13Stdenv; inherit (final) hyprland-protocols hyprland-share-picker; inherit version; }; }; hyprland-share-picker = final: prev: { - hyprland-share-picker = final.libsForQt5.callPackage ./hyprland-share-picker.nix {inherit version;}; + hyprland-share-picker = final.callPackage ./hyprland-share-picker.nix { + inherit (final.qt6) qtbase wrapQtAppsHook qtwayland; + inherit version; + }; + }; + package-overrides = final: prev: { + sdbus-cpp = prev.sdbus-cpp.overrideAttrs (self: super: { + version = "1.3.0"; + src = prev.fetchFromGitHub { + repo = "sdbus-cpp"; + owner = "Kistler-Group"; + rev = "v${self.version}"; + hash = "sha256-S/8/I2wmWukpP+RGPxKbuO44wIExzeYZL49IO+KOqg4="; + }; + }); }; } diff --git a/org.freedesktop.impl.portal.desktop.hyprland.service b/org.freedesktop.impl.portal.desktop.hyprland.service new file mode 100644 index 0000000..2a83e66 --- /dev/null +++ b/org.freedesktop.impl.portal.desktop.hyprland.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.freedesktop.impl.portal.desktop.hyprland +Exec=/usr/lib/xdg-desktop-portal-hyprland +SystemdService=xdg-desktop-portal-hyprland.service \ No newline at end of file diff --git a/protocols/meson.build b/protocols/meson.build index cda43b8..2547daf 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -1,3 +1,13 @@ +wayland_protos = dependency('wayland-protocols', + version: '>=1.31', + default_options: ['tests=false'], +) + +hyprland_protos = dependency('hyprland-protocols', + version: '>=0.2', + fallback: 'hyprland-protocols', +) + wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') hl_protocol_dir = hyprland_protos.get_variable('pkgdatadir') @@ -10,14 +20,12 @@ if wayland_scanner_dep.found() else wayland_scanner = find_program('wayland-scanner', native: true) endif - client_protocols = [ - wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', - wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', - hl_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml', - hl_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml', 'wlr-screencopy-unstable-v1.xml', 'wlr-foreign-toplevel-management-unstable-v1.xml', + hl_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml', + hl_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml', + wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', ] wl_proto_files = [] @@ -33,7 +41,7 @@ foreach xml: client_protocols client_header = custom_target( xml.underscorify() + '_client_h', input: xml, - output: '@BASENAME@-client-protocol.h', + output: '@BASENAME@-protocol.h', command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], ) diff --git a/src/core/PortalManager.cpp b/src/core/PortalManager.cpp new file mode 100644 index 0000000..74b08ad --- /dev/null +++ b/src/core/PortalManager.cpp @@ -0,0 +1,384 @@ +#include "PortalManager.hpp" +#include "../helpers/Log.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { + g_pPortalManager->onGlobal(data, registry, name, interface, version); +} + +void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name) { + ; // noop +} + +inline const wl_registry_listener registryListener = { + .global = handleGlobal, + .global_remove = handleGlobalRemove, +}; + +static void handleOutputGeometry(void* data, struct wl_output* wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, + const char* model, int32_t transform) { + const auto POUTPUT = (SOutput*)data; + + POUTPUT->transform = (wl_output_transform)transform; +} + +static void handleOutputDone(void* data, struct wl_output* wl_output) { + ; +} + +static void handleOutputMode(void* data, struct wl_output* wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { + ; +} + +static void handleOutputScale(void* data, struct wl_output* wl_output, int32_t factor) { + ; +} + +static void handleOutputName(void* data, struct wl_output* wl_output, const char* name) { + if (!name) + return; + + const auto POUTPUT = (SOutput*)data; + POUTPUT->name = name; + + Debug::log(LOG, "Found output name {}", POUTPUT->name); +} + +static void handleOutputDescription(void* data, struct wl_output* wl_output, const char* description) { + ; +} + +inline const wl_output_listener outputListener = { + .geometry = handleOutputGeometry, + .mode = handleOutputMode, + .done = handleOutputDone, + .scale = handleOutputScale, + .name = handleOutputName, + .description = handleOutputDescription, +}; + +static void handleDMABUFFormat(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format) { + ; +} + +static void handleDMABUFModifier(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { + g_pPortalManager->m_vDMABUFMods.push_back({format, (((uint64_t)modifier_hi) << 32) | modifier_lo}); +} + +inline const zwp_linux_dmabuf_v1_listener dmabufListener = { + .format = handleDMABUFFormat, + .modifier = handleDMABUFModifier, +}; + +static void dmabufFeedbackMainDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) { + Debug::log(LOG, "[core] dmabufFeedbackMainDevice"); + + RASSERT(!g_pPortalManager->m_sWaylandConnection.gbm, "double dmabuf feedback"); + + dev_t device; + assert(device_arr->size == sizeof(device)); + memcpy(&device, device_arr->data, sizeof(device)); + + drmDevice* drmDev; + if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { + Debug::log(WARN, "[dmabuf] unable to open main device?"); + exit(1); + } + + g_pPortalManager->m_sWaylandConnection.gbmDevice = g_pPortalManager->createGBMDevice(drmDev); +} + +static void dmabufFeedbackFormatTable(void* data, zwp_linux_dmabuf_feedback_v1* feedback, int fd, uint32_t size) { + Debug::log(TRACE, "[core] dmabufFeedbackFormatTable"); + + g_pPortalManager->m_vDMABUFMods.clear(); + + g_pPortalManager->m_sWaylandConnection.dma.formatTable = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + + if (g_pPortalManager->m_sWaylandConnection.dma.formatTable == MAP_FAILED) { + Debug::log(ERR, "[core] format table failed to mmap"); + g_pPortalManager->m_sWaylandConnection.dma.formatTable = nullptr; + g_pPortalManager->m_sWaylandConnection.dma.formatTableSize = 0; + return; + } + + g_pPortalManager->m_sWaylandConnection.dma.formatTableSize = size; +} + +static void dmabufFeedbackDone(void* data, zwp_linux_dmabuf_feedback_v1* feedback) { + Debug::log(TRACE, "[core] dmabufFeedbackDone"); + + if (g_pPortalManager->m_sWaylandConnection.dma.formatTable) + munmap(g_pPortalManager->m_sWaylandConnection.dma.formatTable, g_pPortalManager->m_sWaylandConnection.dma.formatTableSize); + + g_pPortalManager->m_sWaylandConnection.dma.formatTable = nullptr; + g_pPortalManager->m_sWaylandConnection.dma.formatTableSize = 0; +} + +static void dmabufFeedbackTrancheTargetDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) { + Debug::log(TRACE, "[core] dmabufFeedbackTrancheTargetDevice"); + + dev_t device; + assert(device_arr->size == sizeof(device)); + memcpy(&device, device_arr->data, sizeof(device)); + + drmDevice* drmDev; + if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) + return; + + if (g_pPortalManager->m_sWaylandConnection.gbmDevice) { + drmDevice* drmDevRenderer = NULL; + drmGetDevice2(gbm_device_get_fd(g_pPortalManager->m_sWaylandConnection.gbmDevice), /* flags */ 0, &drmDevRenderer); + g_pPortalManager->m_sWaylandConnection.dma.deviceUsed = drmDevicesEqual(drmDevRenderer, drmDev); + } else { + g_pPortalManager->m_sWaylandConnection.gbmDevice = g_pPortalManager->createGBMDevice(drmDev); + g_pPortalManager->m_sWaylandConnection.dma.deviceUsed = g_pPortalManager->m_sWaylandConnection.gbm; + } +} + +static void dmabufFeedbackTrancheFlags(void* data, zwp_linux_dmabuf_feedback_v1* feedback, uint32_t flags) { + ; +} + +static void dmabufFeedbackTrancheFormats(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* indices) { + Debug::log(TRACE, "[core] dmabufFeedbackTrancheFormats"); + + if (!g_pPortalManager->m_sWaylandConnection.dma.deviceUsed || !g_pPortalManager->m_sWaylandConnection.dma.formatTable) + return; + + struct fm_entry { + uint32_t format; + uint32_t padding; + uint64_t modifier; + }; + // An entry in the table has to be 16 bytes long + assert(sizeof(struct fm_entry) == 16); + + uint32_t n_modifiers = g_pPortalManager->m_sWaylandConnection.dma.formatTableSize / sizeof(struct fm_entry); + fm_entry* fm_entry = (struct fm_entry*)g_pPortalManager->m_sWaylandConnection.dma.formatTable; + uint16_t* idx; + wl_array_for_each(idx, indices) { + if (*idx >= n_modifiers) + continue; + + g_pPortalManager->m_vDMABUFMods.push_back({(fm_entry + *idx)->format, (fm_entry + *idx)->modifier}); + } +} + +static void dmabufFeedbackTrancheDone(void* data, struct zwp_linux_dmabuf_feedback_v1* zwp_linux_dmabuf_feedback_v1) { + Debug::log(TRACE, "[core] dmabufFeedbackTrancheDone"); + + g_pPortalManager->m_sWaylandConnection.dma.deviceUsed = false; +} + +inline const zwp_linux_dmabuf_feedback_v1_listener dmabufFeedbackListener = { + .done = dmabufFeedbackDone, + .format_table = dmabufFeedbackFormatTable, + .main_device = dmabufFeedbackMainDevice, + .tranche_done = dmabufFeedbackTrancheDone, + .tranche_target_device = dmabufFeedbackTrancheTargetDevice, + .tranche_formats = dmabufFeedbackTrancheFormats, + .tranche_flags = dmabufFeedbackTrancheFlags, +}; + +// + +void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { + const std::string INTERFACE = interface; + + Debug::log(LOG, " | Got interface: {} (ver {})", INTERFACE, version); + + if (INTERFACE == zwlr_screencopy_manager_v1_interface.name && m_sPipewire.loop) + m_sPortals.screencopy = std::make_unique((zwlr_screencopy_manager_v1*)wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, version)); + + if (INTERFACE == hyprland_global_shortcuts_manager_v1_interface.name) + m_sPortals.globalShortcuts = std::make_unique( + (hyprland_global_shortcuts_manager_v1*)wl_registry_bind(registry, name, &hyprland_global_shortcuts_manager_v1_interface, version)); + + else if (INTERFACE == hyprland_toplevel_export_manager_v1_interface.name) + m_sWaylandConnection.hyprlandToplevelMgr = wl_registry_bind(registry, name, &hyprland_toplevel_export_manager_v1_interface, version); + + else if (INTERFACE == wl_output_interface.name) { + const auto POUTPUT = m_vOutputs.emplace_back(std::make_unique()).get(); + POUTPUT->output = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version); + wl_output_add_listener(POUTPUT->output, &outputListener, POUTPUT); + POUTPUT->id = name; + } + + else if (INTERFACE == zwp_linux_dmabuf_v1_interface.name) { + if (version < 4) { + Debug::log(ERR, "cannot use linux_dmabuf with ver < 4"); + return; + } + + m_sWaylandConnection.linuxDmabuf = wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version); + m_sWaylandConnection.linuxDmabufFeedback = zwp_linux_dmabuf_v1_get_default_feedback((zwp_linux_dmabuf_v1*)m_sWaylandConnection.linuxDmabuf); + zwp_linux_dmabuf_feedback_v1_add_listener((zwp_linux_dmabuf_feedback_v1*)m_sWaylandConnection.linuxDmabufFeedback, &dmabufFeedbackListener, nullptr); + } + + 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)); +} + +void CPortalManager::onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name) { + std::erase_if(m_vOutputs, [&](const auto& other) { return other->id == name; }); +} + +void CPortalManager::init() { + try { + m_pConnection = sdbus::createDefaultBusConnection("org.freedesktop.impl.portal.desktop.hyprland"); + } catch (std::exception& e) { + Debug::log(CRIT, "Couldn't create the dbus connection ({})", e.what()); + exit(1); + } + + if (!m_pConnection) { + Debug::log(CRIT, "Couldn't connect to dbus"); + exit(1); + } + + // init wayland connection + m_sWaylandConnection.display = wl_display_connect(nullptr); + + if (!m_sWaylandConnection.display) { + Debug::log(CRIT, "Couldn't connect to a wayland compositor"); + exit(1); + } + + if (const auto ENV = getenv("XDG_CURRENT_DESKTOP"); ENV) { + Debug::log(LOG, "XDG_CURRENT_DESKTOP set to {}", ENV); + + if (std::string(ENV) != "Hyprland") + Debug::log(WARN, "Not running on hyprland, some features might be unavailable"); + } else { + Debug::log(WARN, "XDG_CURRENT_DESKTOP unset, running on an unknown desktop"); + } + + wl_registry* registry = wl_display_get_registry(m_sWaylandConnection.display); + wl_registry_add_listener(registry, ®istryListener, nullptr); + + pw_init(nullptr, nullptr); + m_sPipewire.loop = pw_loop_new(nullptr); + + if (!m_sPipewire.loop) + Debug::log(ERR, "Pipewire: refused to create a loop. Screensharing will not work."); + + Debug::log(LOG, "Gathering exported interfaces"); + + wl_display_roundtrip(m_sWaylandConnection.display); + + if (!m_sPortals.screencopy) + Debug::log(WARN, "Screencopy not started: compositor doesn't support zwlr_screencopy_v1 or pw refused a loop"); + else if (m_sWaylandConnection.hyprlandToplevelMgr) + m_sPortals.screencopy->appendToplevelExport(m_sWaylandConnection.hyprlandToplevelMgr); + + wl_display_roundtrip(m_sWaylandConnection.display); + + while (1) { + // dbus events + m_mEventLock.lock(); + + while (m_pConnection->processPendingRequest()) { + ; + } + + std::vector toRemove; + for (auto& t : m_vTimers) { + if (t->passed()) { + t->m_fnCallback(); + toRemove.emplace_back(t.get()); + Debug::log(TRACE, "[core] calling timer {}", (void*)t.get()); + } + } + + while (pw_loop_iterate(m_sPipewire.loop, 0) != 0) { + ; + } + + wl_display_flush(m_sWaylandConnection.display); + if (wl_display_prepare_read(m_sWaylandConnection.display) == 0) { + wl_display_read_events(m_sWaylandConnection.display); + wl_display_dispatch_pending(m_sWaylandConnection.display); + } else { + wl_display_dispatch(m_sWaylandConnection.display); + } + + if (!toRemove.empty()) + std::erase_if(m_vTimers, + [&](const auto& t) { return std::find_if(toRemove.begin(), toRemove.end(), [&](const auto& other) { return other == t.get(); }) != toRemove.end(); }); + + m_mEventLock.unlock(); + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +sdbus::IConnection* CPortalManager::getConnection() { + return m_pConnection.get(); +} + +SOutput* CPortalManager::getOutputFromName(const std::string& name) { + for (auto& o : m_vOutputs) { + if (o->name == name) + return o.get(); + } + return nullptr; +} + +static char* gbm_find_render_node(drmDevice* device) { + drmDevice* devices[64]; + char* render_node = NULL; + + int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0])); + for (int i = 0; i < n; ++i) { + drmDevice* dev = devices[i]; + if (device && !drmDevicesEqual(device, dev)) { + continue; + } + if (!(dev->available_nodes & (1 << DRM_NODE_RENDER))) + continue; + + render_node = strdup(dev->nodes[DRM_NODE_RENDER]); + break; + } + + drmFreeDevices(devices, n); + return render_node; +} + +gbm_device* CPortalManager::createGBMDevice(drmDevice* dev) { + char* renderNode = gbm_find_render_node(dev); + + if (!renderNode) { + Debug::log(ERR, "[core] Couldn't find a render node"); + return nullptr; + } + + Debug::log(TRACE, "[core] createGBMDevice: render node {}", renderNode); + + int fd = open(renderNode, O_RDWR | O_CLOEXEC); + if (fd < 0) { + Debug::log(ERR, "[core] couldn't open render node"); + free(renderNode); + return NULL; + } + + free(renderNode); + return gbm_create_device(fd); +} diff --git a/src/core/PortalManager.hpp b/src/core/PortalManager.hpp new file mode 100644 index 0000000..fc3e15f --- /dev/null +++ b/src/core/PortalManager.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include + +#include "../portals/Screencopy.hpp" +#include "../portals/GlobalShortcuts.hpp" +#include "../helpers/Timer.hpp" +#include "../shared/ToplevelManager.hpp" +#include +#include + +#include + +struct pw_loop; + +struct SOutput { + std::string name; + wl_output* output = nullptr; + uint32_t id = 0; + wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; +}; + +struct SDMABUFModifier { + uint32_t fourcc = 0; + uint64_t mod = 0; +}; + +class CPortalManager { + public: + void init(); + + void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version); + void onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name); + + sdbus::IConnection* getConnection(); + SOutput* getOutputFromName(const std::string& name); + + struct { + pw_loop* loop = nullptr; + } m_sPipewire; + + struct { + std::unique_ptr screencopy; + std::unique_ptr globalShortcuts; + } m_sPortals; + + struct { + std::unique_ptr toplevel; + } m_sHelpers; + + struct { + wl_display* display = nullptr; + void* hyprlandToplevelMgr = nullptr; + void* linuxDmabuf = nullptr; + void* linuxDmabufFeedback = nullptr; + wl_shm* shm = nullptr; + gbm_bo* gbm; + gbm_device* gbmDevice; + struct { + void* formatTable = nullptr; + size_t formatTableSize = 0; + bool deviceUsed = false; + } dma; + } m_sWaylandConnection; + + std::vector m_vDMABUFMods; + + std::vector> m_vTimers; + + gbm_device* createGBMDevice(drmDevice* dev); + + private: + std::unique_ptr m_pConnection; + std::vector> m_vOutputs; + + std::mutex m_mEventLock; +}; + +inline std::unique_ptr g_pPortalManager; \ No newline at end of file diff --git a/src/core/config.c b/src/core/config.c deleted file mode 100644 index 081a0ea..0000000 --- a/src/core/config.c +++ /dev/null @@ -1,194 +0,0 @@ -#include "config.h" -#include "xdpw.h" -#include "logger.h" -#include "screencast_common.h" - -#include -#include -#include -#include -#include - -void print_config(enum LOGLEVEL loglevel, struct xdpw_config *config) { - logprint(loglevel, "config: outputname: %s", config->screencast_conf.output_name); - logprint(loglevel, "config: max_fps: %f", config->screencast_conf.max_fps); - logprint(loglevel, "config: exec_before: %s", config->screencast_conf.exec_before); - logprint(loglevel, "config: exec_after: %s", config->screencast_conf.exec_after); - logprint(loglevel, "config: chooser_cmd: %s", config->screencast_conf.chooser_cmd); - logprint(loglevel, "config: chooser_type: %s", chooser_type_str(config->screencast_conf.chooser_type)); - logprint(loglevel, "config: force_mod_linear: %d", config->screencast_conf.force_mod_linear); -} - -// NOTE: calling finish_config won't prepare the config to be read again from config file -// with init_config since to pointers and other values won't be reset to NULL, or 0 -void finish_config(struct xdpw_config *config) { - logprint(DEBUG, "config: destroying config"); - - // screencast - free(config->screencast_conf.output_name); - free(config->screencast_conf.exec_before); - free(config->screencast_conf.exec_after); - free(config->screencast_conf.chooser_cmd); -} - -static void parse_string(char **dest, const char* value) { - if (value == NULL || *value == '\0') { - logprint(TRACE, "config: skipping empty value in config file"); - return; - } - free(*dest); - *dest = strdup(value); -} - -static void parse_double(double *dest, const char* value) { - if (value == NULL || *value == '\0') { - logprint(TRACE, "config: skipping empty value in config file"); - return; - } - *dest = strtod(value, (char**)NULL); -} - -static void parse_bool(bool *dest, const char* value) { - if (value == NULL || *value == '\0') { - logprint(TRACE, "config: skipping empty value in config file"); - return; - } - if (strcmp(value, "1") == 0) { - *dest = true; - } else { - *dest = false; - } -} - -static int handle_ini_screencast(struct config_screencast *screencast_conf, const char *key, const char *value) { - if (strcmp(key, "output_name") == 0) { - parse_string(&screencast_conf->output_name, value); - } else if (strcmp(key, "max_fps") == 0) { - parse_double(&screencast_conf->max_fps, value); - } else if (strcmp(key, "exec_before") == 0) { - parse_string(&screencast_conf->exec_before, value); - } else if (strcmp(key, "exec_after") == 0) { - parse_string(&screencast_conf->exec_after, value); - } else if (strcmp(key, "chooser_cmd") == 0) { - parse_string(&screencast_conf->chooser_cmd, value); - } else if (strcmp(key, "chooser_type") == 0) { - char *chooser_type = NULL; - parse_string(&chooser_type, value); - screencast_conf->chooser_type = get_chooser_type(chooser_type); - free(chooser_type); - } else if (strcmp(key, "force_mod_linear") == 0) { - parse_bool(&screencast_conf->force_mod_linear, value); - } else { - logprint(TRACE, "config: skipping invalid key in config file"); - return 0; - } - return 1; -} - -static int handle_ini_config(void *data, const char* section, const char *key, const char *value) { - struct xdpw_config *config = (struct xdpw_config*)data; - logprint(TRACE, "config: parsing setction %s, key %s, value %s", section, key, value); - - if (strcmp(section, "screencast") == 0) { - return handle_ini_screencast(&config->screencast_conf, key, value); - } - - logprint(TRACE, "config: skipping invalid key in config file"); - return 0; -} - -static void default_config(struct xdpw_config *config) { - config->screencast_conf.max_fps = 0; - config->screencast_conf.chooser_type = XDPW_CHOOSER_DEFAULT; -} - -static bool file_exists(const char *path) { - return path && access(path, R_OK) != -1; -} - -static char *config_path(const char *prefix, const char *filename) { - if (!prefix || !prefix[0] || !filename || !filename[0]) { - return NULL; - } - - char *config_folder = "xdg-desktop-portal-hyprland"; - - size_t size = 3 + strlen(prefix) + strlen(config_folder) + strlen(filename); - char *path = calloc(size, sizeof(char)); - snprintf(path, size, "%s/%s/%s", prefix, config_folder, filename); - return path; -} - -static char *get_config_path(void) { - const char *home = getenv("HOME"); - char *config_home_fallback = NULL; - if (home != NULL && home[0] != '\0') { - size_t size_fallback = 1 + strlen(home) + strlen("/.config"); - config_home_fallback = calloc(size_fallback, sizeof(char)); - snprintf(config_home_fallback, size_fallback, "%s/.config", home); - } - - const char *config_home = getenv("XDG_CONFIG_HOME"); - if (config_home == NULL || config_home[0] == '\0') { - config_home = config_home_fallback; - } - - const char *prefix[2]; - prefix[0] = config_home; - prefix[1] = SYSCONFDIR "/xdg"; - - const char *xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP"); - const char *config_fallback = "config"; - - char *config_list = NULL; - for (size_t i = 0; i < 2; i++) { - if (xdg_current_desktop) { - config_list = strdup(xdg_current_desktop); - char *config = strtok(config_list, ":"); - while (config) { - char *path = config_path(prefix[i], config); - if (!path) { - config = strtok(NULL, ":"); - continue; - } - logprint(TRACE, "config: trying config file %s", path); - if (file_exists(path)) { - free(config_list); - free(config_home_fallback); - return path; - } - free(path); - config = strtok(NULL, ":"); - } - free(config_list); - } - char *path = config_path(prefix[i], config_fallback); - if (!path) { - continue; - } - logprint(TRACE, "config: trying config file %s", path); - if (file_exists(path)) { - free(config_home_fallback); - return path; - } - free(path); - } - - free(config_home_fallback); - return NULL; -} - -void init_config(char ** const configfile, struct xdpw_config *config) { - if (*configfile == NULL) { - *configfile = get_config_path(); - } - - default_config(config); - if (*configfile == NULL) { - logprint(INFO, "config: no config file found, using the default config"); - return; - } - if (ini_parse(*configfile, handle_ini_config, config) < 0) { - logprint(ERROR, "config: unable to load config file %s", *configfile); - } -} diff --git a/src/core/logger.c b/src/core/logger.c deleted file mode 100644 index a966992..0000000 --- a/src/core/logger.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "logger.h" - -#include -#include -#include -#include - -static struct logger_properties logprops; - -void init_logger(FILE *dst, enum LOGLEVEL level) { - logprops.dst = dst; - logprops.level = level; -} - -enum LOGLEVEL get_loglevel(const char *level) { - if (strcmp(level, "QUIET") == 0) { - return QUIET; - } else if (strcmp(level, "ERROR") == 0) { - return ERROR; - } else if (strcmp(level, "WARN") == 0) { - return WARN; - } else if (strcmp(level, "INFO") == 0) { - return INFO; - } else if (strcmp(level, "DEBUG") == 0) { - return DEBUG; - } else if (strcmp(level, "TRACE") == 0) { - return TRACE; - } - - fprintf(stderr, "Could not understand log level %s\n", level); - exit(1); -} - -static const char *print_loglevel(enum LOGLEVEL loglevel) { - switch (loglevel) { - case QUIET: - return "QUIET"; - case ERROR: - return "ERROR"; - case WARN: - return "WARN"; - case INFO: - return "INFO"; - case DEBUG: - return "DEBUG"; - case TRACE: - return "TRACE"; - } - fprintf(stderr, "Could not find log level %d\n", loglevel); - abort(); -} - -void logprint(enum LOGLEVEL level, char *msg, ...) { - if (!logprops.dst) { - fprintf(stderr, "Logger has been called, but was not initialized\n"); - abort(); - } - - if (level > logprops.level || level == QUIET) { - return; - } - va_list args; - - char timestr[200]; - time_t t = time(NULL); - struct tm *tmp = localtime(&t); - - if (strftime(timestr, sizeof(timestr), "%Y/%m/%d %H:%M:%S", tmp) == 0) { - fprintf(stderr, "strftime returned 0"); - abort(); - } - - fprintf(logprops.dst, "%s", timestr); - fprintf(logprops.dst, " "); - fprintf(logprops.dst, "[%s]", print_loglevel(level)); - fprintf(logprops.dst, " - "); - - va_start(args, msg); - vfprintf(logprops.dst, msg, args); - va_end(args); - - - fprintf(logprops.dst, "\n"); - fflush(logprops.dst); -} diff --git a/src/core/main.c b/src/core/main.c deleted file mode 100644 index 541f0c9..0000000 --- a/src/core/main.c +++ /dev/null @@ -1,291 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "logger.h" -#include "xdpw.h" - -enum event_loop_fd { - EVENT_LOOP_DBUS, - EVENT_LOOP_WAYLAND, - EVENT_LOOP_PIPEWIRE, - EVENT_LOOP_TIMER, -}; - -static const char service_name[] = "org.freedesktop.impl.portal.desktop.hyprland"; - -static int xdpw_usage(FILE *stream, int rc) { - static const char *usage = - "Usage: xdg-desktop-portal-hyprland [options]\n" - "\n" - " -l, --loglevel= Select log level (default is ERROR).\n" - " QUIET, ERROR, WARN, INFO, DEBUG, TRACE\n" - " -c, --config= Select config file.\n" - " (default is $XDG_CONFIG_HOME/xdg-desktop-portal-hyprland/config)\n" - " -r, --replace Replace a running instance.\n" - " -h, --help Get help (this text).\n" - "\n"; - - fprintf(stream, "%s", usage); - return rc; -} - -static int handle_name_lost(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - logprint(INFO, "dbus: lost name, closing connection"); - sd_bus_close(sd_bus_message_get_bus(m)); - return 1; -} - -int main(int argc, char *argv[]) { - struct xdpw_config config = {0}; - char *configfile = NULL; - enum LOGLEVEL loglevel = DEFAULT_LOGLEVEL; - bool replace = false; - - static const char *shortopts = "l:o:c:f:rh"; - static const struct option longopts[] = {{"loglevel", required_argument, NULL, 'l'}, - {"config", required_argument, NULL, 'c'}, - {"replace", no_argument, NULL, 'r'}, - {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0}}; - - while (1) { - int c = getopt_long(argc, argv, shortopts, longopts, NULL); - if (c < 0) { - break; - } - - switch (c) { - case 'l': - loglevel = get_loglevel(optarg); - break; - case 'c': - configfile = strdup(optarg); - break; - case 'r': - replace = true; - break; - case 'h': - return xdpw_usage(stdout, EXIT_SUCCESS); - default: - return xdpw_usage(stderr, EXIT_FAILURE); - } - } - - init_logger(stderr, loglevel); - init_config(&configfile, &config); - print_config(DEBUG, &config); - - int ret = 0; - - sd_bus *bus = NULL; - ret = sd_bus_open_user(&bus); - if (ret < 0) { - logprint(ERROR, "dbus: failed to connect to user bus: %s", strerror(-ret)); - return EXIT_FAILURE; - } - logprint(DEBUG, "dbus: connected"); - - struct wl_display *wl_display = wl_display_connect(NULL); - if (!wl_display) { - logprint(ERROR, "wayland: failed to connect to display"); - sd_bus_unref(bus); - return EXIT_FAILURE; - } - logprint(DEBUG, "wlroots: wl_display connected"); - - pw_init(NULL, NULL); - struct pw_loop *pw_loop = pw_loop_new(NULL); - if (!pw_loop) { - logprint(ERROR, "pipewire: failed to create loop"); - wl_display_disconnect(wl_display); - sd_bus_unref(bus); - return EXIT_FAILURE; - } - logprint(DEBUG, "pipewire: pw_loop created"); - - struct xdpw_state state = { - .bus = bus, - .wl_display = wl_display, - .pw_loop = pw_loop, - .screencast_source_types = MONITOR, - .screencast_cursor_modes = HIDDEN | EMBEDDED, - .screencast_version = XDP_CAST_PROTO_VER, - .screenshot_version = XDP_SHOT_PROTO_VER, - .config = &config, - }; - - wl_list_init(&state.xdpw_sessions); - wl_list_init(&state.restore_tokens); - - initShortcutsInstance(&state, &state.shortcutsInstance); - - ret = xdpw_screenshot_init(&state); - if (ret < 0) { - logprint(ERROR, "xdpw: failed to initialize screenshot"); - goto error; - } - - ret = xdpw_screencast_init(&state); - if (ret < 0) { - logprint(ERROR, "xdpw: failed to initialize screencast"); - goto error; - } - - uint64_t flags = SD_BUS_NAME_ALLOW_REPLACEMENT; - if (replace) { - flags |= SD_BUS_NAME_REPLACE_EXISTING; - } - - ret = sd_bus_request_name(bus, service_name, flags); - if (ret < 0) { - logprint(ERROR, "dbus: failed to acquire service name: %s", strerror(-ret)); - goto error; - } - - const char *unique_name; - ret = sd_bus_get_unique_name(bus, &unique_name); - if (ret < 0) { - logprint(ERROR, "dbus: failed to get unique bus name: %s", strerror(-ret)); - goto error; - } - - static char match[1024]; - snprintf(match, sizeof(match), - "sender='org.freedesktop.DBus'," - "type='signal'," - "interface='org.freedesktop.DBus'," - "member='NameOwnerChanged'," - "path='/org/freedesktop/DBus'," - "arg0='%s'," - "arg1='%s'", - service_name, unique_name); - - sd_bus_slot *slot; - ret = sd_bus_add_match(bus, &slot, match, handle_name_lost, NULL); - if (ret < 0) { - logprint(ERROR, "dbus: failed to add NameOwnerChanged signal match: %s", strerror(-ret)); - goto error; - } - - wl_list_init(&state.timers); - - struct pollfd pollfds[] = {[EVENT_LOOP_DBUS] = - { - .fd = sd_bus_get_fd(state.bus), - .events = POLLIN, - }, - [EVENT_LOOP_WAYLAND] = - { - .fd = wl_display_get_fd(state.wl_display), - .events = POLLIN, - }, - [EVENT_LOOP_PIPEWIRE] = - { - .fd = pw_loop_get_fd(state.pw_loop), - .events = POLLIN, - }, - [EVENT_LOOP_TIMER] = { - .fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC), - .events = POLLIN, - }}; - - state.timer_poll_fd = pollfds[EVENT_LOOP_TIMER].fd; - - while (1) { - ret = poll(pollfds, sizeof(pollfds) / sizeof(pollfds[0]), -1); - if (ret < 0) { - logprint(ERROR, "poll failed: %s", strerror(errno)); - goto error; - } - - if (pollfds[EVENT_LOOP_DBUS].revents & POLLHUP) { - logprint(INFO, "event-loop: disconnected from dbus"); - break; - } - if (pollfds[EVENT_LOOP_WAYLAND].revents & POLLHUP) { - logprint(INFO, "event-loop: disconnected from wayland"); - break; - } - if (pollfds[EVENT_LOOP_PIPEWIRE].revents & POLLHUP) { - logprint(INFO, "event-loop: disconnected from pipewire"); - break; - } - - if (pollfds[EVENT_LOOP_DBUS].revents & POLLIN) { - logprint(TRACE, "event-loop: got dbus event"); - do { - ret = sd_bus_process(state.bus, NULL); - } while (ret > 0); - if (ret < 0) { - logprint(ERROR, "sd_bus_process failed: %s", strerror(-ret)); - goto error; - } - } - - if (pollfds[EVENT_LOOP_WAYLAND].revents & POLLIN) { - logprint(TRACE, "event-loop: got wayland event"); - ret = wl_display_dispatch(state.wl_display); - if (ret < 0) { - logprint(ERROR, "wl_display_dispatch failed: %s", strerror(errno)); - goto error; - } - } - - if (pollfds[EVENT_LOOP_PIPEWIRE].revents & POLLIN) { - logprint(TRACE, "event-loop: got pipewire event"); - ret = pw_loop_iterate(state.pw_loop, 0); - if (ret < 0) { - logprint(ERROR, "pw_loop_iterate failed: %s", spa_strerror(ret)); - goto error; - } - } - - if (pollfds[EVENT_LOOP_TIMER].revents & POLLIN) { - logprint(TRACE, "event-loop: got a timer event"); - - int timer_fd = pollfds[EVENT_LOOP_TIMER].fd; - uint64_t expirations; - ssize_t n = read(timer_fd, &expirations, sizeof(expirations)); - if (n < 0) { - logprint(ERROR, "failed to read from timer FD\n"); - goto error; - } - - struct xdpw_timer *timer = state.next_timer; - if (timer != NULL) { - xdpw_event_loop_timer_func_t func = timer->func; - void *user_data = timer->user_data; - xdpw_destroy_timer(timer); - - func(user_data); - } - } - - do { - ret = wl_display_dispatch_pending(state.wl_display); - wl_display_flush(state.wl_display); - } while (ret > 0); - - sd_bus_flush(state.bus); - } - - // TODO: cleanup - finish_config(&config); - free(configfile); - - return EXIT_SUCCESS; - -error: - sd_bus_unref(bus); - pw_loop_leave(state.pw_loop); - pw_loop_destroy(state.pw_loop); - wl_display_disconnect(state.wl_display); - return EXIT_FAILURE; -} diff --git a/src/core/request.c b/src/core/request.c deleted file mode 100644 index 064e9d9..0000000 --- a/src/core/request.c +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include -#include -#include "xdpw.h" -#include "logger.h" - -static const char interface_name[] = "org.freedesktop.impl.portal.Request"; - -static int method_close(sd_bus_message *msg, void *data, - sd_bus_error *ret_error) { - struct xdpw_request *req = data; - int ret = 0; - logprint(INFO, "dbus: request closed"); - - sd_bus_message *reply = NULL; - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - - sd_bus_message_unref(reply); - - xdpw_request_destroy(req); - - return 0; -} - -static const sd_bus_vtable request_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("Close", "", "", method_close, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -struct xdpw_request *xdpw_request_create(sd_bus *bus, const char *object_path) { - struct xdpw_request *req = calloc(1, sizeof(struct xdpw_request)); - - if (sd_bus_add_object_vtable(bus, &req->slot, object_path, interface_name, - request_vtable, NULL) < 0) { - free(req); - logprint(ERROR, "dbus: sd_bus_add_object_vtable failed: %s", - strerror(-errno)); - return NULL; - } - - return req; -} - -void xdpw_request_destroy(struct xdpw_request *req) { - if (req == NULL) { - return; - } - sd_bus_slot_unref(req->slot); - free(req); -} diff --git a/src/core/session.c b/src/core/session.c deleted file mode 100644 index 7fd3a80..0000000 --- a/src/core/session.c +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include -#include -#include -#include -#include "xdpw.h" -#include "screencast.h" -#include "logger.h" - -static const char interface_name[] = "org.freedesktop.impl.portal.Session"; - -static int method_close(sd_bus_message *msg, void *data, - sd_bus_error *ret_error) { - int ret = 0; - struct xdpw_session *sess = data; - logprint(INFO, "dbus: session closed"); - - sd_bus_message *reply = NULL; - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - - sd_bus_message_unref(reply); - - xdpw_session_destroy(sess); - - return 0; -} - -static const sd_bus_vtable session_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("Close", "", "", method_close, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -struct xdpw_session *xdpw_session_create(struct xdpw_state *state, sd_bus *bus, char *object_path) { - struct xdpw_session *sess = calloc(1, sizeof(struct xdpw_session)); - - sess->session_handle = object_path; - - if (sd_bus_add_object_vtable(bus, &sess->slot, object_path, interface_name, - session_vtable, sess) < 0) { - free(sess); - logprint(ERROR, "dbus: sd_bus_add_object_vtable failed: %s", - strerror(-errno)); - return NULL; - } - - wl_list_insert(&state->xdpw_sessions, &sess->link); - return sess; -} - -void xdpw_session_destroy(struct xdpw_session *sess) { - logprint(DEBUG, "dbus: destroying session %p", sess); - if (!sess) { - return; - } - struct xdpw_screencast_instance *cast = sess->screencast_instance; - if (cast) { - assert(cast->refcount > 0); - --cast->refcount; - logprint(DEBUG, "xdpw: screencast instance %p now has %d references", - cast, cast->refcount); - if (cast->refcount < 1) { - if (cast->frame_state == XDPW_FRAME_STATE_NONE) { - logprint(TRACE, "xdpw: screencast instance not streaming, destroy it"); - xdpw_screencast_instance_destroy(cast); - } else if (cast->teardown) { - logprint(TRACE, "xdpw: screencast instance marked for teardown, destroy it"); - xdpw_screencast_instance_destroy(cast); - } else { - logprint(TRACE, "xdpw: screencast instance still streaming, set quit flag"); - cast->quit = true; - } - } - } - - sd_bus_slot_unref(sess->slot); - wl_list_remove(&sess->link); - free(sess->session_handle); - free(sess); -} diff --git a/src/core/timer.c b/src/core/timer.c deleted file mode 100644 index 4d21d29..0000000 --- a/src/core/timer.c +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include -#include - -#include "xdpw.h" -#include "logger.h" -#include "timespec_util.h" - -static void update_timer(struct xdpw_state *state) { - int timer_fd = state->timer_poll_fd; - if (timer_fd < 0) { - return; - } - - bool updated = false; - struct xdpw_timer *timer; - wl_list_for_each(timer, &state->timers, link) { - if (state->next_timer == NULL || - timespec_less(&timer->at, &state->next_timer->at)) { - state->next_timer = timer; - updated = true; - } - } - - if (updated) { - struct itimerspec delay = { .it_value = state->next_timer->at }; - errno = 0; - int ret = timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &delay, NULL); - if (ret < 0) { - fprintf(stderr, "failed to timerfd_settime(): %s\n", - strerror(errno)); - } - } -} - -struct xdpw_timer *xdpw_add_timer(struct xdpw_state *state, - uint64_t delay_ns, xdpw_event_loop_timer_func_t func, void *data) { - struct xdpw_timer *timer = calloc(1, sizeof(struct xdpw_timer)); - if (timer == NULL) { - logprint(ERROR, "Timer allocation failed"); - return NULL; - } - timer->state = state; - timer->func = func; - timer->user_data = data; - wl_list_insert(&state->timers, &timer->link); - - clock_gettime(CLOCK_MONOTONIC, &timer->at); - timespec_add(&timer->at, delay_ns); - - update_timer(state); - return timer; -} - -void xdpw_destroy_timer(struct xdpw_timer *timer) { - if (timer == NULL) { - return; - } - struct xdpw_state *state = timer->state; - - if (state->next_timer == timer) { - state->next_timer = NULL; - } - - wl_list_remove(&timer->link); - free(timer); - - update_timer(state); -} diff --git a/src/core/timespec_util.c b/src/core/timespec_util.c deleted file mode 100644 index b00c428..0000000 --- a/src/core/timespec_util.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "timespec_util.h" -#include - -void timespec_add(struct timespec *t, int64_t delta_ns) { - int delta_ns_low = delta_ns % TIMESPEC_NSEC_PER_SEC; - int delta_s_high = delta_ns / TIMESPEC_NSEC_PER_SEC; - - t->tv_sec += delta_s_high; - - t->tv_nsec += (long)delta_ns_low; - if (t->tv_nsec >= TIMESPEC_NSEC_PER_SEC) { - t->tv_nsec -= TIMESPEC_NSEC_PER_SEC; - ++t->tv_sec; - } -} - -bool timespec_less(struct timespec *t1, struct timespec *t2) { - if (t1->tv_sec != t2->tv_sec) { - return t1->tv_sec < t2->tv_sec; - } - return t1->tv_nsec < t2->tv_nsec; -} - -bool timespec_is_zero(struct timespec *t) { - return t->tv_sec == 0 && t->tv_nsec == 0; -} - -int64_t timespec_diff_ns(struct timespec *t1, struct timespec *t2) { - int64_t s = t1->tv_sec - t2->tv_sec; - int64_t ns = t1->tv_nsec - t2->tv_nsec; - - return s * TIMESPEC_NSEC_PER_SEC + ns; -} diff --git a/src/core/utils.c b/src/core/utils.c deleted file mode 100644 index 1add9ad..0000000 --- a/src/core/utils.c +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include "utils.h" - -char *getFormat(const char *fmt, ...) { - char *outputStr = NULL; - - va_list args; - va_start(args, fmt); - vasprintf(&outputStr, fmt, args); - va_end(args); - - return outputStr; -} diff --git a/src/globalshortcuts/global_shortcuts.c b/src/globalshortcuts/global_shortcuts.c deleted file mode 100644 index 7a55f68..0000000 --- a/src/globalshortcuts/global_shortcuts.c +++ /dev/null @@ -1,537 +0,0 @@ -#include "include/global_shortcuts.h" - -#include - -#include "include/xdpw.h" - -static void wlr_registry_handle_add(void *data, struct wl_registry *reg, uint32_t id, const char *interface, uint32_t ver) { - struct globalShortcutsInstance *instance = data; - - if (!strcmp(interface, hyprland_global_shortcuts_manager_v1_interface.name) && instance->manager == NULL) { - uint32_t version = ver; - - logprint(DEBUG, "hyprland: |-- registered to interface %s (Version %u)", interface, version); - - instance->manager = wl_registry_bind(reg, id, &hyprland_global_shortcuts_manager_v1_interface, version); - } -} - -static void wlr_registry_handle_remove(void *data, struct wl_registry *reg, uint32_t id) { - ; // ignored -} - -static const struct wl_registry_listener wlr_registry_listener = { - .global = wlr_registry_handle_add, - .global_remove = wlr_registry_handle_remove, -}; - -static const char object_path[] = "/org/freedesktop/portal/desktop"; -static const char interface_name[] = "org.freedesktop.impl.portal.GlobalShortcuts"; - -static int method_gs_create_session(sd_bus_message *msg, void *data, sd_bus_error *ret_error); -static int method_gs_bind_shortcuts(sd_bus_message *msg, void *data, sd_bus_error *ret_error); -static int method_gs_list_shortcuts(sd_bus_message *msg, void *data, sd_bus_error *ret_error); - -static const sd_bus_vtable gs_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("CreateSession", "oosa{sv}", "ua{sv}", method_gs_create_session, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("BindShortcuts", "ooa(sa{sv})sa{sv}", "ua{sv}", method_gs_bind_shortcuts, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListShortcuts", "oo", "ua{sv}", method_gs_list_shortcuts, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_SIGNAL("Activated", "osta{sv}", 0), - SD_BUS_SIGNAL("Deactivated", "osta{sv}", 0), - SD_BUS_SIGNAL("ShortcutsChanged", "oa(sa{sv})", 0), - SD_BUS_VTABLE_END}; - -static void handleActivated(void *data, struct hyprland_global_shortcut_v1 *hyprland_global_shortcut_v1, uint32_t tv_sec_hi, uint32_t tv_sec_lo, - uint32_t tv_nsec) { - struct xdpw_state *state = data; - - bool found = false; - - struct globalShortcut *curr; - struct globalShortcutsClient *currc; - wl_list_for_each(currc, &state->shortcutsInstance.shortcutClients, link) { - wl_list_for_each(curr, &currc->shortcuts, link) { - if (curr->hlShortcut == hyprland_global_shortcut_v1) { - found = true; - goto found; - } - } - } -found: - - if (!found) return; - - sd_bus_emit_signal(state->bus, object_path, interface_name, "Activated", "osta{sv}", currc->session->session_handle, curr->name, - ((uint64_t)tv_sec_hi << 32) | (uint64_t)(tv_sec_lo), 0); -} - -static void handleDeactivated(void *data, struct hyprland_global_shortcut_v1 *hyprland_global_shortcut_v1, uint32_t tv_sec_hi, uint32_t tv_sec_lo, - uint32_t tv_nsec) { - struct xdpw_state *state = data; - - bool found = false; - - struct globalShortcut *curr; - struct globalShortcutsClient *currc; - wl_list_for_each(currc, &state->shortcutsInstance.shortcutClients, link) { - wl_list_for_each(curr, &currc->shortcuts, link) { - if (curr->hlShortcut == hyprland_global_shortcut_v1) { - found = true; - goto found; - } - } - } -found: - - if (!found) return; - - sd_bus_emit_signal(state->bus, object_path, interface_name, "Deactivated", "osta{sv}", currc->session->session_handle, curr->name, - ((uint64_t)tv_sec_hi << 32) | (uint64_t)(tv_sec_lo), 0); -} - -static const struct hyprland_global_shortcut_v1_listener shortcutListener = { - .pressed = handleActivated, - .released = handleDeactivated, -}; - -static int method_gs_create_session(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { - struct xdpw_state *state = data; - struct globalShortcutsClient *client = calloc(1, sizeof(struct globalShortcutsClient)); - wl_list_insert(&state->shortcutsInstance.shortcutClients, &client->link); - wl_list_init(&client->shortcuts); - - int ret = 0; - - logprint(INFO, "dbus: create session method invoked"); - - char *request_handle, *session_handle, *app_id; - ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id); - if (ret < 0) { - return ret; - } - - ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - - logprint(INFO, "dbus: request_handle: %s", request_handle); - logprint(INFO, "dbus: session_handle: %s", session_handle); - logprint(INFO, "dbus: app_id: %s", app_id); - - char *key; - char *option_token; - int innerRet = 0; - while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { - innerRet = sd_bus_message_read(msg, "s", &key); - if (innerRet < 0) { - return innerRet; - } - - if (strcmp(key, "session_handle_token") == 0) { - sd_bus_message_read(msg, "v", "s", &option_token); - logprint(INFO, "dbus: option token: %s", option_token); - } else if (strcmp(key, "shortcuts") == 0) { - // init shortcuts - client->sentShortcuts = true; - - innerRet = sd_bus_message_enter_container(msg, 'v', "a(sa{sv})"); - if (innerRet < 0) { - return innerRet; - } - - innerRet = sd_bus_message_enter_container(msg, 'a', "(sa{sv})"); - - while (innerRet > 0) { - char type; - char *container; - sd_bus_message_peek_type(msg, &type, &container); - - if (type != 'r') break; - - innerRet = sd_bus_message_enter_container(msg, 'r', "sa{sv}"); - if (innerRet == -ENXIO) break; - - sd_bus_message_peek_type(msg, &type, &container); - - innerRet = sd_bus_message_read(msg, "s", &key); - - if (innerRet == -ENXIO) break; - - if (innerRet < 0) { - return innerRet; - } - - logprint(DEBUG, "shortcut name %s", key); - - struct globalShortcut *shortcut = calloc(1, sizeof(struct globalShortcut)); - shortcut->name = malloc(strlen(key) + 1); - strcpy(shortcut->name, key); - - ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - - while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { - innerRet = sd_bus_message_read(msg, "s", &key); - if (innerRet < 0) { - return innerRet; - } - - if (strcmp(key, "description") == 0) { - sd_bus_message_read(msg, "v", "s", &key); - shortcut->description = malloc(strlen(key) + 1); - strcpy(shortcut->description, key); - } else { - sd_bus_message_skip(msg, "v"); - } - - innerRet = sd_bus_message_exit_container(msg); - if (innerRet < 0) { - return innerRet; - } - } - - sd_bus_message_exit_container(msg); - - if (shortcut->description == NULL) { - shortcut->description = calloc(1, 1); - } - - wl_list_insert(&client->shortcuts, &shortcut->link); - - sd_bus_message_exit_container(msg); - } - - innerRet = sd_bus_message_exit_container(msg); - innerRet = sd_bus_message_exit_container(msg); - if (innerRet < 0) { - return innerRet; - } - - } else { - logprint(WARN, "dbus: unknown option: %s", key); - sd_bus_message_skip(msg, "v"); - } - - innerRet = sd_bus_message_exit_container(msg); - if (innerRet < 0) { - return innerRet; - } - } - if (ret < 0) { - return ret; - } - - ret = sd_bus_message_exit_container(msg); - if (ret < 0) { - return ret; - } - - struct xdpw_request *req = xdpw_request_create(sd_bus_message_get_bus(msg), request_handle); - if (req == NULL) { - return -ENOMEM; - } - - struct xdpw_session *sess = xdpw_session_create(state, sd_bus_message_get_bus(msg), strdup(session_handle)); - if (sess == NULL) { - return -ENOMEM; - } - - client->session = sess; - - sess->app_id = malloc(strlen(app_id) + 1); - strcpy(sess->app_id, app_id); - - sd_bus_message *reply = NULL; - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_append(reply, "u", PORTAL_RESPONSE_SUCCESS, 0); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_open_container(reply, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - sd_bus_message_open_container(reply, 'e', "sv"); - sd_bus_message_append(reply, "s", "shortcuts"); - sd_bus_message_open_container(reply, 'v', "a(sa{sv})"); - sd_bus_message_open_container(reply, 'a', "(sa{sv})"); - struct globalShortcut *curr; - wl_list_for_each(curr, &client->shortcuts, link) { - sd_bus_message_append(reply, "(sa{sv})", curr->name, 1, "description", "s", curr->description); - curr->hlShortcut = hyprland_global_shortcuts_manager_v1_register_shortcut( - state->shortcutsInstance.manager, curr->name, (strlen(app_id) == 0 ? option_token : app_id), curr->description, ""); - hyprland_global_shortcut_v1_add_listener(curr->hlShortcut, &shortcutListener, state); - curr->bound = true; - } - - ret = sd_bus_message_close_container(reply); - ret = sd_bus_message_close_container(reply); - ret = sd_bus_message_close_container(reply); - ret = sd_bus_message_close_container(reply); - if (ret < 0) { - return ret; - } - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - - sd_bus_message_unref(reply); - return 0; -} - -static int method_gs_bind_shortcuts(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { - struct xdpw_state *state = data; - - int ret = 0; - - logprint(INFO, "dbus: bind shortcuts invoked"); - - char *request_handle, *session_handle; - ret = sd_bus_message_read(msg, "oo", &request_handle, &session_handle); - if (ret < 0) { - return ret; - } - - logprint(INFO, "dbus: request_handle: %s", request_handle); - logprint(INFO, "dbus: session_handle: %s", session_handle); - - struct xdpw_session *session, *tmp_session; - wl_list_for_each_reverse_safe(session, tmp_session, &state->xdpw_sessions, link) { - if (strcmp(session->session_handle, session_handle) == 0) { - logprint(DEBUG, "dbus: bind shortcuts: found matching session %s", session->session_handle); - break; - } - } - - struct globalShortcutsClient *client, *tmp_client; - wl_list_for_each_reverse_safe(client, tmp_client, &state->shortcutsInstance.shortcutClients, link) { - if (strcmp(client->session, session_handle) == 0) { - logprint(DEBUG, "dbus: bind shortcuts: found matching client %s", client->session); - break; - } - } - - if (!client->sentShortcuts) { - char *key; - int innerRet = 0; - client->sentShortcuts = true; - - innerRet = sd_bus_message_enter_container(msg, 'a', "(sa{sv})"); - - while (innerRet > 0) { - char type; - char *container; - sd_bus_message_peek_type(msg, &type, &container); - - if (type != 'r') break; - - innerRet = sd_bus_message_enter_container(msg, 'r', "sa{sv}"); - if (innerRet == -ENXIO) break; - - sd_bus_message_peek_type(msg, &type, &container); - - innerRet = sd_bus_message_read(msg, "s", &key); - - if (innerRet == -ENXIO) break; - - if (innerRet < 0) { - return innerRet; - } - - logprint(DEBUG, "shortcut name %s", key); - - struct globalShortcut *shortcut = calloc(1, sizeof(struct globalShortcut)); - shortcut->name = malloc(strlen(key) + 1); - strcpy(shortcut->name, key); - - ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - - while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { - innerRet = sd_bus_message_read(msg, "s", &key); - if (innerRet < 0) { - return innerRet; - } - - if (strcmp(key, "description") == 0) { - sd_bus_message_read(msg, "v", "s", &key); - shortcut->description = malloc(strlen(key) + 1); - strcpy(shortcut->description, key); - } else { - sd_bus_message_skip(msg, "v"); - } - - innerRet = sd_bus_message_exit_container(msg); - if (innerRet < 0) { - return innerRet; - } - } - - sd_bus_message_exit_container(msg); - - if (shortcut->description == NULL) { - shortcut->description = calloc(1, 1); - } - - wl_list_insert(&client->shortcuts, &shortcut->link); - - sd_bus_message_exit_container(msg); - } - - innerRet = sd_bus_message_exit_container(msg); - if (innerRet < 0) { - return innerRet; - } - } - - ret = sd_bus_message_exit_container(msg); - if (ret < 0) { - return ret; - } - - char *parent_window; - ret = sd_bus_message_read(msg, "s", &parent_window); - - logprint(DEBUG, "dbus: parent_window %s", parent_window); - - client->parent_window = malloc(strlen(parent_window) + 1); - strcpy(client->parent_window, parent_window); - - sd_bus_message *reply = NULL; - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_append(reply, "u", PORTAL_RESPONSE_SUCCESS, 0); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_open_container(reply, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - sd_bus_message_open_container(reply, 'e', "sv"); - sd_bus_message_append(reply, "s", "shortcuts"); - sd_bus_message_open_container(reply, 'v', "a(sa{sv})"); - sd_bus_message_open_container(reply, 'a', "(sa{sv})"); - struct globalShortcut *curr; - wl_list_for_each(curr, &client->shortcuts, link) { - sd_bus_message_append(reply, "(sa{sv})", curr->name, 1, "description", "s", curr->description); - if (!curr->bound) { - curr->hlShortcut = hyprland_global_shortcuts_manager_v1_register_shortcut(state->shortcutsInstance.manager, curr->name, parent_window, - curr->description, ""); - hyprland_global_shortcut_v1_add_listener(curr->hlShortcut, &shortcutListener, state); - } - } - - ret = sd_bus_message_close_container(reply); - ret = sd_bus_message_close_container(reply); - ret = sd_bus_message_close_container(reply); - ret = sd_bus_message_close_container(reply); - if (ret < 0) { - return ret; - } - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - - return 0; -} - -static int method_gs_list_shortcuts(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { - struct xdpw_state *state = data; - - int ret = 0; - - logprint(INFO, "dbus: list shortcuts invoked"); - - char *request_handle, *session_handle; - ret = sd_bus_message_read(msg, "oo", &request_handle, &session_handle); - if (ret < 0) { - return ret; - } - - logprint(INFO, "dbus: request_handle: %s", request_handle); - logprint(INFO, "dbus: session_handle: %s", session_handle); - - bool foundClient = false; - struct globalShortcutsClient *client, *tmp_client; - wl_list_for_each(client, &state->shortcutsInstance.shortcutClients, link) { - if (strcmp(client->session, session_handle) == 0) { - logprint(DEBUG, "dbus: bind shortcuts: found matching client %s", client->session); - foundClient = true; - break; - } - } - - if (!foundClient || !client->sentShortcuts) return 0; - - sd_bus_message *reply = NULL; - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_append(reply, "u", PORTAL_RESPONSE_SUCCESS, 0); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_open_container(reply, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - sd_bus_message_open_container(reply, 'e', "sv"); - sd_bus_message_append(reply, "s", "shortcuts"); - sd_bus_message_open_container(reply, 'v', "a(sa{sv})"); - sd_bus_message_open_container(reply, 'a', "(sa{sv})"); - struct globalShortcut *curr; - wl_list_for_each(curr, &client->shortcuts, link) { - sd_bus_message_append(reply, "(sa{sv})", curr->name, 1, "description", "s", curr->description); - } - - ret = sd_bus_message_close_container(reply); - ret = sd_bus_message_close_container(reply); - ret = sd_bus_message_close_container(reply); - ret = sd_bus_message_close_container(reply); - if (ret < 0) { - return ret; - } - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - - return 0; -} - -void initShortcutsInstance(struct xdpw_state *state, struct globalShortcutsInstance *instance) { - instance->manager = NULL; - wl_list_init(&instance->shortcutClients); - - struct wl_registry *registry = wl_display_get_registry(state->wl_display); - wl_registry_add_listener(registry, &wlr_registry_listener, instance); - - wl_display_roundtrip(state->wl_display); - - if (instance->manager == NULL) { - logprint(ERROR, "hyprland shortcut protocol unavailable!"); - return; - } - - sd_bus_slot *slot = NULL; - int ret = sd_bus_add_object_vtable(state->bus, &slot, object_path, interface_name, gs_vtable, state); - - logprint(DEBUG, "dbus: gs: ret bind %d", ret); - - // register to hl -} \ No newline at end of file diff --git a/src/helpers/Log.cpp b/src/helpers/Log.cpp new file mode 100644 index 0000000..013db6c --- /dev/null +++ b/src/helpers/Log.cpp @@ -0,0 +1 @@ +#include "Log.hpp" diff --git a/src/helpers/Log.hpp b/src/helpers/Log.hpp new file mode 100644 index 0000000..97783c4 --- /dev/null +++ b/src/helpers/Log.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +enum eLogLevel +{ + TRACE = 0, + INFO, + LOG, + WARN, + ERR, + CRIT +}; + +#define RASSERT(expr, reason, ...) \ + if (!(expr)) { \ + Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ + std::format(reason, ##__VA_ARGS__), __LINE__, \ + ([]() constexpr->std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \ + printf("Assertion failed! See the log in /tmp/hypr/hyprland.log for more info."); \ + *((int*)nullptr) = 1; /* so that we crash and get a coredump */ \ + } + +#define ASSERT(expr) RASSERT(expr, "?") + +namespace Debug { + inline bool quiet = false; + inline bool verbose = false; + + template + void log(eLogLevel level, const std::string& fmt, Args&&... args) { + + if (!verbose && level == TRACE) + return; + + if (quiet) + return; + + std::cout << '['; + + switch (level) { + case TRACE: std::cout << "TRACE"; break; + case INFO: std::cout << "INFO"; break; + case LOG: std::cout << "LOG"; break; + case WARN: std::cout << "WARN"; break; + case ERR: std::cout << "ERR"; break; + case CRIT: std::cout << "CRITICAL"; break; + } + + std::cout << "] "; + + std::cout << std::vformat(fmt, std::make_format_args(args...)) << "\n"; + } +}; \ No newline at end of file diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp new file mode 100644 index 0000000..956a663 --- /dev/null +++ b/src/helpers/MiscFunctions.cpp @@ -0,0 +1,17 @@ +#include "MiscFunctions.hpp" +#include +#include "../helpers/Log.hpp" + +std::string execAndGet(const char* cmd) { + std::array buffer; + std::string result; + const std::unique_ptr pipe(popen(cmd, "r"), pclose); + if (!pipe) { + Debug::log(ERR, "execAndGet: failed in pipe"); + return ""; + } + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + return result; +} \ No newline at end of file diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp new file mode 100644 index 0000000..ccefb68 --- /dev/null +++ b/src/helpers/MiscFunctions.hpp @@ -0,0 +1,4 @@ +#pragma once +#include + +std::string execAndGet(const char* cmd); \ No newline at end of file diff --git a/src/helpers/Timer.cpp b/src/helpers/Timer.cpp new file mode 100644 index 0000000..0857e66 --- /dev/null +++ b/src/helpers/Timer.cpp @@ -0,0 +1,11 @@ +#include "Timer.hpp" + +CTimer::CTimer(float ms, std::function callback) { + m_fDuration = ms; + m_tStart = std::chrono::high_resolution_clock::now(); + m_fnCallback = callback; +} + +bool CTimer::passed() const { + return std::chrono::high_resolution_clock::now() > (m_tStart + std::chrono::milliseconds((uint64_t)m_fDuration)); +} \ No newline at end of file diff --git a/src/helpers/Timer.hpp b/src/helpers/Timer.hpp new file mode 100644 index 0000000..b5832a4 --- /dev/null +++ b/src/helpers/Timer.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +class CTimer { + public: + CTimer(float ms, std::function callback); + + bool passed() const; + + std::function m_fnCallback; + + private: + std::chrono::high_resolution_clock::time_point m_tStart; + float m_fDuration; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..d7a4482 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,41 @@ +#include + +#include "helpers/Log.hpp" +#include "core/PortalManager.hpp" + +void printHelp() { + std::cout << R"#(| xdg-desktop-portal-hyprland +| -------------------------------------- +| -v (--verbose) > enable trace logging +| -q (--quiet) > disable logging +| -h (--help) > print this menu +)#"; +} + +int main(int argc, char** argv, char** envp) { + g_pPortalManager = std::make_unique(); + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + + if (arg == "--verbose" || arg == "-v") + Debug::verbose = true; + + else if (arg == "--quiet" || arg == "-q") + Debug::quiet = true; + + else if (arg == "--help" || arg == "-h") { + printHelp(); + return 0; + } else { + printHelp(); + return 1; + } + } + + Debug::log(LOG, "Initializing xdph..."); + + g_pPortalManager->init(); + + return 0; +} \ No newline at end of file diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..ec0fd1d --- /dev/null +++ b/src/meson.build @@ -0,0 +1,21 @@ +globber = run_command('find', '.', '-name', '*.cpp', check: true) +src = globber.stdout().strip().split('\n') + +executable('xdg-desktop-portal-hyprland', + [src, wl_proto_files], + dependencies: [ + dependency('cairo'), + dependency('gbm'), + dependency('libdrm'), + dependency('libjpeg'), + dependency('libpipewire-0.3'), + dependency('pango'), + dependency('pangocairo'), + dependency('sdbus-c++'), + dependency('threads'), + dependency('wayland-client'), + ], + include_directories: inc, + install: true, + install_dir: get_option('libexecdir') +) diff --git a/src/portals/GlobalShortcuts.cpp b/src/portals/GlobalShortcuts.cpp new file mode 100644 index 0000000..37712aa --- /dev/null +++ b/src/portals/GlobalShortcuts.cpp @@ -0,0 +1,237 @@ +#include "GlobalShortcuts.hpp" +#include "../core/PortalManager.hpp" +#include "../helpers/Log.hpp" + +// wayland + +static void handleActivated(void* data, hyprland_global_shortcut_v1* hyprland_global_shortcut_v1, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + const auto PKEYBIND = (SKeybind*)data; + + g_pPortalManager->m_sPortals.globalShortcuts->onActivated(PKEYBIND, ((uint64_t)tv_sec_hi << 32) | (uint64_t)(tv_sec_lo)); +} + +static void handleDeactivated(void* data, hyprland_global_shortcut_v1* hyprland_global_shortcut_v1, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + const auto PKEYBIND = (SKeybind*)data; + + g_pPortalManager->m_sPortals.globalShortcuts->onDeactivated(PKEYBIND, ((uint64_t)tv_sec_hi << 32) | (uint64_t)(tv_sec_lo)); +} + +static const hyprland_global_shortcut_v1_listener shortcutListener = { + .pressed = handleActivated, + .released = handleDeactivated, +}; + +// + +CGlobalShortcutsPortal::SSession* CGlobalShortcutsPortal::getSession(sdbus::ObjectPath& path) { + for (auto& s : m_vSessions) { + if (s->sessionHandle == path) + return s.get(); + } + + return nullptr; +} + +void CGlobalShortcutsPortal::onCreateSession(sdbus::MethodCall& call) { + sdbus::ObjectPath requestHandle, sessionHandle; + + call >> requestHandle; + call >> sessionHandle; + + std::string appID; + call >> appID; + + Debug::log(LOG, "[globalshortcuts] New session:"); + Debug::log(LOG, "[globalshortcuts] | {}", requestHandle.c_str()); + Debug::log(LOG, "[globalshortcuts] | {}", sessionHandle.c_str()); + Debug::log(LOG, "[globalshortcuts] | appid: {}", appID); + + const auto PSESSION = m_vSessions.emplace_back(std::make_unique(appID, requestHandle, sessionHandle)).get(); + + // create objects + PSESSION->session = createDBusSession(sessionHandle); + PSESSION->session->onDestroy = [PSESSION]() { PSESSION->session.release(); }; + PSESSION->request = createDBusRequest(requestHandle); + PSESSION->request->onDestroy = [PSESSION]() { PSESSION->request.release(); }; + + std::unordered_map opts; + call >> opts; + + for (auto& [k, v] : opts) { + if (k == "shortcuts") { + PSESSION->registered = true; + + std::vector>> shortcuts; + + shortcuts = v.get>>>(); + + for (auto& s : shortcuts) { + const auto PSHORTCUT = PSESSION->keybinds.emplace_back(std::make_unique()).get(); + + PSHORTCUT->id = s.get<0>(); + + std::unordered_map data = s.get<1>(); + + for (auto& [k, v] : data) { + if (k == "description") { + PSHORTCUT->description = v.get(); + } else { + Debug::log(LOG, "[globalshortcuts] unknown shortcut data type {}", k); + } + } + + PSHORTCUT->shortcut = + hyprland_global_shortcuts_manager_v1_register_shortcut(m_sState.manager, PSHORTCUT->id.c_str(), PSESSION->appid.c_str(), PSHORTCUT->description.c_str(), ""); + hyprland_global_shortcut_v1_add_listener(PSHORTCUT->shortcut, &shortcutListener, PSHORTCUT); + + PSHORTCUT->session = PSESSION; + } + + Debug::log(LOG, "[globalshortcuts] registered {} shortcuts", shortcuts.size()); + } + } + + auto reply = call.createReply(); + reply << (uint32_t)0; + reply << std::unordered_map{}; + reply.send(); +} + +void CGlobalShortcutsPortal::onBindShortcuts(sdbus::MethodCall& call) { + sdbus::ObjectPath sessionHandle, requestHandle; + call >> requestHandle; + call >> sessionHandle; + + Debug::log(LOG, "[globalshortcuts] Bind keys:"); + Debug::log(LOG, "[globalshortcuts] | {}", sessionHandle.c_str()); + + std::vector>> shortcuts; + std::vector>> shortcutsToReturn; + call >> shortcuts; + + const auto PSESSION = getSession(sessionHandle); + + if (!PSESSION) { + Debug::log(ERR, "[globalshortcuts] No session?"); + return; + } + + PSESSION->registered = true; + + for (auto& s : shortcuts) { + const auto PSHORTCUT = PSESSION->keybinds.emplace_back(std::make_unique()).get(); + + PSHORTCUT->id = s.get<0>(); + + std::unordered_map data = s.get<1>(); + + for (auto& [k, v] : data) { + if (k == "description") { + PSHORTCUT->description = v.get(); + } else { + Debug::log(LOG, "[globalshortcuts] unknown shortcut data type {}", k); + } + } + + PSHORTCUT->shortcut = + hyprland_global_shortcuts_manager_v1_register_shortcut(m_sState.manager, PSHORTCUT->id.c_str(), PSESSION->appid.c_str(), PSHORTCUT->description.c_str(), ""); + hyprland_global_shortcut_v1_add_listener(PSHORTCUT->shortcut, &shortcutListener, PSHORTCUT); + + PSHORTCUT->session = PSESSION; + + std::unordered_map shortcutData; + shortcutData["description"] = PSHORTCUT->description; + shortcutData["trigger_description"] = ""; + shortcutsToReturn.push_back({PSHORTCUT->id, shortcutData}); + } + + Debug::log(LOG, "[globalshortcuts] registered {} shortcuts", shortcuts.size()); + + auto reply = call.createReply(); + + std::unordered_map data; + data["shortcuts"] = shortcutsToReturn; + + reply << (uint32_t)0; + reply << data; + reply.send(); +} + +void CGlobalShortcutsPortal::onListShortcuts(sdbus::MethodCall& call) { + sdbus::ObjectPath sessionHandle, requestHandle; + call >> requestHandle; + call >> sessionHandle; + + Debug::log(LOG, "[globalshortcuts] List keys:"); + Debug::log(LOG, "[globalshortcuts] | {}", sessionHandle.c_str()); + + const auto PSESSION = getSession(sessionHandle); + + if (!PSESSION) { + Debug::log(ERR, "[globalshortcuts] No session?"); + return; + } + + std::vector>> shortcuts; + + for (auto& s : PSESSION->keybinds) { + std::unordered_map opts; + opts["description"] = s->description; + opts["trigger_description"] = ""; + shortcuts.push_back({s->id, opts}); + } + + auto reply = call.createReply(); + + std::unordered_map data; + data["shortcuts"] = shortcuts; + + reply << (uint32_t)0; + reply << data; + reply.send(); +} + +CGlobalShortcutsPortal::CGlobalShortcutsPortal(hyprland_global_shortcuts_manager_v1* mgr) { + m_sState.manager = mgr; + + m_pObject = sdbus::createObject(*g_pPortalManager->getConnection(), OBJECT_PATH); + + m_pObject->registerMethod(INTERFACE_NAME, "CreateSession", "oosa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onCreateSession(c); }); + m_pObject->registerMethod(INTERFACE_NAME, "BindShortcuts", "ooa(sa{sv})sa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onBindShortcuts(c); }); + m_pObject->registerMethod(INTERFACE_NAME, "ListShortcuts", "oo", "ua{sv}", [&](sdbus::MethodCall c) { onListShortcuts(c); }); + m_pObject->registerSignal(INTERFACE_NAME, "Activated", "osta{sv}"); + m_pObject->registerSignal(INTERFACE_NAME, "Deactivated", "osta{sv}"); + m_pObject->registerSignal(INTERFACE_NAME, "ShortcutsChanged", "oa(sa{sv})"); + + m_pObject->finishRegistration(); + + Debug::log(LOG, "[globalshortcuts] registered"); +} + +void CGlobalShortcutsPortal::onActivated(SKeybind* pKeybind, uint64_t time) { + const auto PSESSION = (CGlobalShortcutsPortal::SSession*)pKeybind->session; + + Debug::log(TRACE, "[gs] Session {} called activated on {}", (void*)PSESSION, pKeybind->id); + + auto signal = m_pObject->createSignal(INTERFACE_NAME, "Activated"); + signal << PSESSION->sessionHandle; + signal << pKeybind->id; + signal << time; + signal << std::unordered_map{}; + + m_pObject->emitSignal(signal); +} + +void CGlobalShortcutsPortal::onDeactivated(SKeybind* pKeybind, uint64_t time) { + const auto PSESSION = (CGlobalShortcutsPortal::SSession*)pKeybind->session; + + Debug::log(TRACE, "[gs] Session {} called deactivated on {}", (void*)PSESSION, pKeybind->id); + + auto signal = m_pObject->createSignal(INTERFACE_NAME, "Deactivated"); + signal << PSESSION->sessionHandle; + signal << pKeybind->id; + signal << time; + signal << std::unordered_map{}; + + m_pObject->emitSignal(signal); +} \ No newline at end of file diff --git a/src/portals/GlobalShortcuts.hpp b/src/portals/GlobalShortcuts.hpp new file mode 100644 index 0000000..0d4d987 --- /dev/null +++ b/src/portals/GlobalShortcuts.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include "../shared/Session.hpp" + +struct SKeybind { + std::string id, description, preferredTrigger; + hyprland_global_shortcut_v1* shortcut = nullptr; + void* session = nullptr; +}; + +class CGlobalShortcutsPortal { + public: + CGlobalShortcutsPortal(hyprland_global_shortcuts_manager_v1* mgr); + + void onCreateSession(sdbus::MethodCall& call); + void onBindShortcuts(sdbus::MethodCall& call); + void onListShortcuts(sdbus::MethodCall& call); + + void onActivated(SKeybind* pKeybind, uint64_t time); + void onDeactivated(SKeybind* pKeybind, uint64_t time); + + struct SSession { + std::string appid; + sdbus::ObjectPath requestHandle, sessionHandle; + std::unique_ptr request; + std::unique_ptr session; + + bool registered = false; + + std::vector> keybinds; + }; + + std::vector> m_vSessions; + + private: + struct { + hyprland_global_shortcuts_manager_v1* manager; + } m_sState; + + std::unique_ptr m_pObject; + + SSession* getSession(sdbus::ObjectPath& path); + + const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.GlobalShortcuts"; + const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop"; +}; \ No newline at end of file diff --git a/src/portals/Screencopy.cpp b/src/portals/Screencopy.cpp new file mode 100644 index 0000000..e4c8857 --- /dev/null +++ b/src/portals/Screencopy.cpp @@ -0,0 +1,1310 @@ +#include "Screencopy.hpp" +#include "../core/PortalManager.hpp" +#include "../helpers/Log.hpp" +#include "../helpers/MiscFunctions.hpp" + +#include +#include +#include + +// --------------- Wayland Protocol Handlers --------------- // + +static void wlrOnBuffer(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)PSESSION); + + PSESSION->sharingData.frameInfoSHM.w = width; + PSESSION->sharingData.frameInfoSHM.h = height; + PSESSION->sharingData.frameInfoSHM.fmt = drmFourccFromSHM((wl_shm_format)format); + PSESSION->sharingData.frameInfoSHM.size = stride * height; + PSESSION->sharingData.frameInfoSHM.stride = stride; + + // todo: done if ver < 3 +} + +static void wlrOnFlags(void* data, zwlr_screencopy_frame_v1* frame, uint32_t flags) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnFlags for {}", (void*)PSESSION); + + // todo: maybe check for y invert? +} + +static void wlrOnReady(void* data, zwlr_screencopy_frame_v1* frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)PSESSION); + + PSESSION->sharingData.status = FRAME_READY; + + PSESSION->sharingData.tvSec = ((((uint64_t)tv_sec_hi) << 32) + (uint64_t)tv_sec_lo); + PSESSION->sharingData.tvNsec = tv_nsec; + PSESSION->sharingData.tvTimestampNs = PSESSION->sharingData.tvSec * SPA_NSEC_PER_SEC + PSESSION->sharingData.tvNsec; + + Debug::log(TRACE, "[sc] frame timestamp sec: {} nsec: {} combined: {}ns", PSESSION->sharingData.tvSec, PSESSION->sharingData.tvNsec, PSESSION->sharingData.tvTimestampNs); + + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(PSESSION); + + g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION); + + zwlr_screencopy_frame_v1_destroy(frame); + PSESSION->sharingData.frameCallback = nullptr; +} + +static void wlrOnFailed(void* data, zwlr_screencopy_frame_v1* frame) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnFailed for {}", (void*)PSESSION); + + PSESSION->sharingData.status = FRAME_FAILED; +} + +static void wlrOnDamage(void* data, zwlr_screencopy_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnDamage for {}", (void*)PSESSION); + + if (PSESSION->sharingData.damageCount > 3) { + PSESSION->sharingData.damage[0] = {0, 0, PSESSION->sharingData.frameInfoDMA.w, PSESSION->sharingData.frameInfoDMA.h}; + return; + } + + PSESSION->sharingData.damage[PSESSION->sharingData.damageCount++] = {x, y, width, height}; + + Debug::log(TRACE, "[sc] wlr damage: {} {} {} {}", x, y, width, height); +} + +static void wlrOnDmabuf(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)PSESSION); + + PSESSION->sharingData.frameInfoDMA.w = width; + PSESSION->sharingData.frameInfoDMA.h = height; + PSESSION->sharingData.frameInfoDMA.fmt = format; +} + +static void wlrOnBufferDone(void* data, zwlr_screencopy_frame_v1* frame) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)PSESSION); + + const auto PSTREAM = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(PSESSION); + + if (!PSTREAM) { + Debug::log(TRACE, "[sc] wlrOnBufferDone: no stream"); + zwlr_screencopy_frame_v1_destroy(frame); + PSESSION->sharingData.frameCallback = nullptr; + PSESSION->sharingData.status = FRAME_NONE; + return; + } + + Debug::log(TRACE, "[sc] pw format {} size {}x{}", (int)PSTREAM->pwVideoInfo.format, PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height); + Debug::log(TRACE, "[sc] wlr format {} size {}x{}", (int)PSESSION->sharingData.frameInfoSHM.fmt, PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h); + + const auto FMT = PSTREAM->isDMA ? PSESSION->sharingData.frameInfoDMA.fmt : PSESSION->sharingData.frameInfoSHM.fmt; + if ((PSTREAM->pwVideoInfo.format != pwFromDrmFourcc(FMT) && PSTREAM->pwVideoInfo.format != pwStripAlpha(pwFromDrmFourcc(FMT))) || + (PSTREAM->pwVideoInfo.size.width != PSESSION->sharingData.frameInfoDMA.w || PSTREAM->pwVideoInfo.size.height != PSESSION->sharingData.frameInfoDMA.h)) { + Debug::log(LOG, "[sc] Incompatible formats, renegotiate stream"); + PSESSION->sharingData.status = FRAME_RENEG; + zwlr_screencopy_frame_v1_destroy(frame); + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM); + g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION); + PSESSION->sharingData.status = FRAME_NONE; + return; + } + + if (!PSTREAM->currentPWBuffer) { + Debug::log(TRACE, "[sc] wlrOnBufferDone: dequeue, no current buffer"); + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->dequeue(PSESSION); + } + + if (!PSTREAM->currentPWBuffer) { + zwlr_screencopy_frame_v1_destroy(frame); + PSESSION->sharingData.frameCallback = nullptr; + Debug::log(LOG, "[screencopy/pipewire] Out of buffers"); + PSESSION->sharingData.status = FRAME_NONE; + return; + } + + zwlr_screencopy_frame_v1_copy_with_damage(frame, PSTREAM->currentPWBuffer->wlBuffer); + + Debug::log(TRACE, "[sc] wlr frame copied"); +} + +static const zwlr_screencopy_frame_v1_listener wlrFrameListener = { + .buffer = wlrOnBuffer, + .flags = wlrOnFlags, + .ready = wlrOnReady, + .failed = wlrOnFailed, + .damage = wlrOnDamage, + .linux_dmabuf = wlrOnDmabuf, + .buffer_done = wlrOnBufferDone, +}; + +static void hlOnBuffer(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] hlOnBuffer for {}", (void*)PSESSION); + + PSESSION->sharingData.frameInfoSHM.w = width; + PSESSION->sharingData.frameInfoSHM.h = height; + PSESSION->sharingData.frameInfoSHM.fmt = drmFourccFromSHM((wl_shm_format)format); + PSESSION->sharingData.frameInfoSHM.size = stride * height; + PSESSION->sharingData.frameInfoSHM.stride = stride; + + // todo: done if ver < 3 +} + +static void hlOnFlags(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t flags) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] hlOnFlags for {}", (void*)PSESSION); + + // todo: maybe check for y invert? +} + +static void hlOnReady(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] hlOnReady for {}", (void*)PSESSION); + + PSESSION->sharingData.status = FRAME_READY; + + PSESSION->sharingData.tvSec = ((((uint64_t)tv_sec_hi) << 32) + (uint64_t)tv_sec_lo); + PSESSION->sharingData.tvNsec = tv_nsec; + PSESSION->sharingData.tvTimestampNs = PSESSION->sharingData.tvSec * SPA_NSEC_PER_SEC + PSESSION->sharingData.tvNsec; + + Debug::log(TRACE, "[sc] frame timestamp sec: {} nsec: {} combined: {}ns", PSESSION->sharingData.tvSec, PSESSION->sharingData.tvNsec, PSESSION->sharingData.tvTimestampNs); + + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(PSESSION); + + g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION); + + hyprland_toplevel_export_frame_v1_destroy(frame); + PSESSION->sharingData.windowFrameCallback = nullptr; +} + +static void hlOnFailed(void* data, hyprland_toplevel_export_frame_v1* frame) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] hlOnFailed for {}", (void*)PSESSION); + + PSESSION->sharingData.status = FRAME_FAILED; +} + +static void hlOnDamage(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] hlOnDamage for {}", (void*)PSESSION); + + if (PSESSION->sharingData.damageCount > 3) { + PSESSION->sharingData.damage[0] = {0, 0, PSESSION->sharingData.frameInfoDMA.w, PSESSION->sharingData.frameInfoDMA.h}; + return; + } + + PSESSION->sharingData.damage[PSESSION->sharingData.damageCount++] = {x, y, width, height}; + + Debug::log(TRACE, "[sc] hl damage: {} {} {} {}", x, y, width, height); +} + +static void hlOnDmabuf(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] hlOnDmabuf for {}", (void*)PSESSION); + + PSESSION->sharingData.frameInfoDMA.w = width; + PSESSION->sharingData.frameInfoDMA.h = height; + PSESSION->sharingData.frameInfoDMA.fmt = format; +} + +static void hlOnBufferDone(void* data, hyprland_toplevel_export_frame_v1* frame) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] hlOnBufferDone for {}", (void*)PSESSION); + + const auto PSTREAM = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(PSESSION); + + if (!PSTREAM) { + Debug::log(TRACE, "[sc] hlOnBufferDone: no stream"); + hyprland_toplevel_export_frame_v1_destroy(frame); + PSESSION->sharingData.windowFrameCallback = nullptr; + PSESSION->sharingData.status = FRAME_NONE; + return; + } + + Debug::log(TRACE, "[sc] pw format {} size {}x{}", (int)PSTREAM->pwVideoInfo.format, PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height); + Debug::log(TRACE, "[sc] hl format {} size {}x{}", (int)PSESSION->sharingData.frameInfoSHM.fmt, PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h); + + const auto FMT = PSTREAM->isDMA ? PSESSION->sharingData.frameInfoDMA.fmt : PSESSION->sharingData.frameInfoSHM.fmt; + if ((PSTREAM->pwVideoInfo.format != pwFromDrmFourcc(FMT) && PSTREAM->pwVideoInfo.format != pwStripAlpha(pwFromDrmFourcc(FMT))) || + (PSTREAM->pwVideoInfo.size.width != PSESSION->sharingData.frameInfoDMA.w || PSTREAM->pwVideoInfo.size.height != PSESSION->sharingData.frameInfoDMA.h)) { + Debug::log(LOG, "[sc] Incompatible formats, renegotiate stream"); + PSESSION->sharingData.status = FRAME_RENEG; + hyprland_toplevel_export_frame_v1_destroy(frame); + PSESSION->sharingData.windowFrameCallback = nullptr; + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM); + g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION); + PSESSION->sharingData.status = FRAME_NONE; + return; + } + + if (!PSTREAM->currentPWBuffer) { + Debug::log(TRACE, "[sc] wlrOnBufferDone: dequeue, no current buffer"); + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->dequeue(PSESSION); + } + + if (!PSTREAM->currentPWBuffer) { + hyprland_toplevel_export_frame_v1_destroy(frame); + PSESSION->sharingData.windowFrameCallback = nullptr; + Debug::log(LOG, "[screencopy/pipewire] Out of buffers"); + PSESSION->sharingData.status = FRAME_NONE; + return; + } + + hyprland_toplevel_export_frame_v1_copy(frame, PSTREAM->currentPWBuffer->wlBuffer, false); + + Debug::log(TRACE, "[sc] wlr frame copied"); +} + +static const hyprland_toplevel_export_frame_v1_listener hyprlandFrameListener = { + .buffer = hlOnBuffer, + .damage = hlOnDamage, + .flags = hlOnFlags, + .ready = hlOnReady, + .failed = hlOnFailed, + .linux_dmabuf = hlOnDmabuf, + .buffer_done = hlOnBufferDone, +}; + +// --------------------------------------------------------- // + +void CScreencopyPortal::onCreateSession(sdbus::MethodCall& call) { + sdbus::ObjectPath requestHandle, sessionHandle; + + call >> requestHandle; + call >> sessionHandle; + + std::string appID; + call >> appID; + + Debug::log(LOG, "[screencopy] New session:"); + Debug::log(LOG, "[screencopy] | {}", requestHandle.c_str()); + Debug::log(LOG, "[screencopy] | {}", sessionHandle.c_str()); + Debug::log(LOG, "[screencopy] | appid: {}", appID); + + const auto PSESSION = m_vSessions.emplace_back(std::make_unique(appID, requestHandle, sessionHandle)).get(); + + // create objects + PSESSION->session = createDBusSession(sessionHandle); + PSESSION->session->onDestroy = [PSESSION]() { PSESSION->session.release(); }; + PSESSION->request = createDBusRequest(requestHandle); + PSESSION->request->onDestroy = [PSESSION]() { PSESSION->request.release(); }; + + auto reply = call.createReply(); + reply << (uint32_t)0; + reply << std::unordered_map{}; + reply.send(); +} + +void CScreencopyPortal::onSelectSources(sdbus::MethodCall& call) { + sdbus::ObjectPath requestHandle, sessionHandle; + + call >> requestHandle; + call >> sessionHandle; + + std::string appID; + call >> appID; + + Debug::log(LOG, "[screencopy] SelectSources:"); + Debug::log(LOG, "[screencopy] | {}", requestHandle.c_str()); + Debug::log(LOG, "[screencopy] | {}", sessionHandle.c_str()); + Debug::log(LOG, "[screencopy] | appid: {}", appID); + + const auto PSESSION = getSession(sessionHandle); + + if (!PSESSION) { + Debug::log(ERR, "[screencopy] SelectSources: no session found??"); + auto reply = call.createErrorReply(sdbus::Error{"NOSESSION", "No session found"}); + reply << (uint32_t)1; + reply.send(); + return; + } + + std::unordered_map options; + + call >> options; + + struct { + bool exists = false; + std::string token, output; + uint64_t windowHandle; + bool withCursor; + uint64_t timeIssued; + } restoreData; + + for (auto& [key, val] : options) { + + if (key == "cursor_mode") { + PSESSION->cursorMode = val.get(); + Debug::log(LOG, "[screencopy] option cursor_mode to {}", PSESSION->cursorMode); + } else if (key == "restore_data") { + // suv + // v -> r(susbt) -> v2 + // v -> a(sv) -> v3 + std::string issuer; + uint32_t version; + auto suv = val.get>(); + issuer = suv.get<0>(); + version = suv.get<1>(); + + sdbus::Variant data = suv.get<2>(); + + if (issuer != "hyprland") { + Debug::log(LOG, "[screencopy] Restore token from {}, ignoring", issuer); + continue; + } + + Debug::log(LOG, "[screencopy] Restore token from {} ver {}", issuer, version); + + if (version != 2 && version != 3) { + Debug::log(LOG, "[screencopy] Restore token ver unsupported, skipping", issuer); + continue; + } + + if (version == 2) { + auto susbt = data.get>(); + + restoreData.exists = true; + + restoreData.token = susbt.get<0>(); + restoreData.windowHandle = susbt.get<1>(); + restoreData.output = susbt.get<2>(); + restoreData.withCursor = susbt.get<3>(); + restoreData.timeIssued = susbt.get<4>(); + + Debug::log(LOG, "[screencopy] Restore token v2 {} with data: {} {} {} {}", restoreData.token, restoreData.windowHandle, restoreData.output, restoreData.withCursor, + restoreData.timeIssued); + } else { + // ver 3 + auto sv = data.get>(); + + uint64_t windowHandle = 0; + std::string windowClass; + + restoreData.windowHandle = 0; + restoreData.exists = true; + + for (auto& [tkkey, tkval] : sv) { + if (tkkey == "output") { + restoreData.output = tkval.get(); + } else if (tkkey == "windowHandle") { + windowHandle = tkval.get(); + } else if (tkkey == "windowClass") { + windowClass = tkval.get(); + } else if (tkkey == "withCursor") { + restoreData.withCursor = (bool)tkval.get(); + } else if (tkkey == "timeIssued") { + restoreData.timeIssued = tkval.get(); + } else if (tkkey == "token") { + restoreData.token = tkval.get(); + } else { + Debug::log(LOG, "[screencopy] restore token v3, unknown prop {}", tkkey); + } + } + + Debug::log(LOG, "[screencopy] Restore token v3 {} with data: {} {} {} {} {}", restoreData.token, windowHandle, windowClass, restoreData.output, + restoreData.withCursor, restoreData.timeIssued); + + // find window + if (windowHandle != 0 || !windowClass.empty()) { + if (windowHandle != 0) { + for (auto& h : g_pPortalManager->m_sHelpers.toplevel->m_vToplevels) { + if ((uint64_t)h->handle == windowHandle) { + restoreData.windowHandle = (uint64_t)h->handle; + Debug::log(LOG, "[screencopy] token v3 window found by handle {}", (void*)windowHandle); + break; + } + } + } + + if (restoreData.windowHandle == 0 && !windowClass.empty()) { + // try class + for (auto& h : g_pPortalManager->m_sHelpers.toplevel->m_vToplevels) { + if (h->windowClass == windowClass) { + restoreData.windowHandle = (uint64_t)h->handle; + Debug::log(LOG, "[screencopy] token v3 window found by class {}", windowClass); + break; + } + } + } + } + } + + } else if (key == "persist_mode") { + PSESSION->persistMode = val.get(); + Debug::log(LOG, "[screencopy] option persist_mode to {}", PSESSION->persistMode); + } else { + Debug::log(LOG, "[screencopy] unused option {}", key); + } + } + + const bool RESTOREDATAVALID = restoreData.exists && + (g_pPortalManager->m_sHelpers.toplevel->exists((zwlr_foreign_toplevel_handle_v1*)restoreData.windowHandle) || g_pPortalManager->getOutputFromName(restoreData.output)); + + SSelectionData SHAREDATA; + if (RESTOREDATAVALID) { + Debug::log(LOG, "[screencopy] restore data valid, not prompting"); + + SHAREDATA.output = restoreData.output; + SHAREDATA.windowHandle = (zwlr_foreign_toplevel_handle_v1*)restoreData.windowHandle; + SHAREDATA.type = restoreData.windowHandle ? TYPE_WINDOW : TYPE_OUTPUT; + PSESSION->cursorMode = restoreData.withCursor; + } else { + Debug::log(LOG, "[screencopy] restore data invalid / missing, prompting"); + + SHAREDATA = promptForScreencopySelection(); + } + + Debug::log(LOG, "[screencopy] SHAREDATA returned selection {}", (int)SHAREDATA.type); + + if (SHAREDATA.type == TYPE_WINDOW && !m_sState.toplevel) { + Debug::log(ERR, "[screencopy] Requested type window for no toplevel export protocol!"); + SHAREDATA.type = TYPE_INVALID; + } + + PSESSION->selection = SHAREDATA; + + auto reply = call.createReply(); + reply << (uint32_t)(SHAREDATA.type == TYPE_INVALID ? 1 : 0); + reply << std::unordered_map{}; + reply.send(); +} + +void CScreencopyPortal::onStart(sdbus::MethodCall& call) { + sdbus::ObjectPath requestHandle, sessionHandle; + + call >> requestHandle; + call >> sessionHandle; + + std::string appID, parentWindow; + call >> appID; + call >> parentWindow; + + Debug::log(LOG, "[screencopy] Start:"); + Debug::log(LOG, "[screencopy] | {}", requestHandle.c_str()); + Debug::log(LOG, "[screencopy] | {}", sessionHandle.c_str()); + Debug::log(LOG, "[screencopy] | appid: {}", appID); + Debug::log(LOG, "[screencopy] | parent_window: {}", parentWindow); + + const auto PSESSION = getSession(sessionHandle); + + if (!PSESSION) { + Debug::log(ERR, "[screencopy] Start: no session found??"); + auto reply = call.createErrorReply(sdbus::Error{"NOSESSION", "No session found"}); + reply << (uint32_t)1; + reply.send(); + return; + } + + startSharing(PSESSION); + + auto reply = call.createReply(); + reply << (uint32_t)0; + + std::unordered_map options; + + if (PSESSION->persistMode != 0 && PSESSION->selection.allowToken) { + // give them a token :) + std::unordered_map mapData; + + switch (PSESSION->selection.type) { + case TYPE_GEOMETRY: + case TYPE_OUTPUT: mapData["output"] = PSESSION->selection.output; break; + case TYPE_WINDOW: + mapData["windowHandle"] = (uint64_t)PSESSION->selection.windowHandle; + for (auto& w : g_pPortalManager->m_sHelpers.toplevel->m_vToplevels) { + if (w->handle == PSESSION->selection.windowHandle) { + mapData["windowClass"] = w->windowClass; + break; + } + } + break; + default: Debug::log(ERR, "[screencopy] wonk selection in token saving"); break; + } + mapData["timeIssued"] = uint64_t(time(nullptr)); + mapData["token"] = std::string("todo"); + mapData["withCursor"] = PSESSION->cursorMode; + + sdbus::Variant restoreData{mapData}; + sdbus::Struct fullRestoreStruct{"hyprland", 3, restoreData}; + options["restore_data"] = sdbus::Variant{fullRestoreStruct}; + + Debug::log(LOG, "[screencopy] Sent restore token to {}", PSESSION->sessionHandle.c_str()); + } + + uint32_t type = 0; + switch (PSESSION->selection.type) { + case TYPE_OUTPUT: type = 1 << MONITOR; break; + case TYPE_WINDOW: type = 1 << WINDOW; break; + case TYPE_GEOMETRY: + case TYPE_WORKSPACE: type = 1 << VIRTUAL; break; + default: type = 0; break; + } + options["source_type"] = type; + + std::vector>> streams; + + std::unordered_map streamData; + streamData["position"] = sdbus::Variant{sdbus::Struct{0, 0}}; + streamData["size"] = sdbus::Variant{sdbus::Struct{PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h}}; + streamData["source_type"] = sdbus::Variant{uint32_t{type}}; + streams.emplace_back(sdbus::Struct>{PSESSION->sharingData.nodeID, streamData}); + + options["streams"] = streams; + + reply << options; + + reply.send(); +} + +void CScreencopyPortal::startSharing(CScreencopyPortal::SSession* pSession) { + pSession->sharingData.active = true; + + startFrameCopy(pSession); + + wl_display_dispatch(g_pPortalManager->m_sWaylandConnection.display); + wl_display_roundtrip(g_pPortalManager->m_sWaylandConnection.display); + + if (pSession->sharingData.frameInfoDMA.fmt == DRM_FORMAT_INVALID) { + Debug::log(ERR, "[screencopy] Couldn't obtain a format from dma"); // todo: blocks shm + return; + } + + m_pPipewire->createStream(pSession); + + while (pSession->sharingData.nodeID == SPA_ID_INVALID) { + int ret = pw_loop_iterate(g_pPortalManager->m_sPipewire.loop, 0); + if (ret < 0) { + Debug::log(ERR, "[pipewire] pw_loop_iterate failed with {}", spa_strerror(ret)); + return; + } + } + + Debug::log(LOG, "[screencopy] Sharing initialized"); + + g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(pSession); + + Debug::log(TRACE, "[sc] queued frame in {}ms", 1000.0 / pSession->sharingData.framerate); +} + +void CScreencopyPortal::startFrameCopy(CScreencopyPortal::SSession* pSession) { + const auto POUTPUT = g_pPortalManager->getOutputFromName(pSession->selection.output); + + if (!pSession->sharingData.active) { + Debug::log(TRACE, "[sc] startFrameCopy: not copying, inactive session"); + return; + } + + if (!POUTPUT && (pSession->selection.type == TYPE_GEOMETRY || pSession->selection.type == TYPE_OUTPUT)) { + Debug::log(ERR, "[screencopy] Output {} not found??", pSession->selection.output); + return; + } + + if ((pSession->sharingData.frameCallback && (pSession->selection.type == TYPE_GEOMETRY || pSession->selection.type == TYPE_OUTPUT)) || + (pSession->sharingData.windowFrameCallback && pSession->selection.type == TYPE_WINDOW)) { + Debug::log(ERR, "[screencopy] tried scheduling on already scheduled cb (type {})", (int)pSession->selection.type); + return; + } + + if (pSession->selection.type == TYPE_GEOMETRY) { + pSession->sharingData.frameCallback = zwlr_screencopy_manager_v1_capture_output_region(m_sState.screencopy, pSession->cursorMode, POUTPUT->output, pSession->selection.x, + pSession->selection.y, pSession->selection.w, pSession->selection.h); + pSession->sharingData.transform = POUTPUT->transform; + } else if (pSession->selection.type == TYPE_OUTPUT) { + pSession->sharingData.frameCallback = zwlr_screencopy_manager_v1_capture_output(m_sState.screencopy, pSession->cursorMode, POUTPUT->output); + pSession->sharingData.transform = POUTPUT->transform; + } else if (pSession->selection.type == TYPE_WINDOW) { + if (!pSession->selection.windowHandle) { + Debug::log(ERR, "[screencopy] selected invalid window?"); + return; + } + pSession->sharingData.windowFrameCallback = + hyprland_toplevel_export_manager_v1_capture_toplevel_with_wlr_toplevel_handle(m_sState.toplevel, pSession->cursorMode, pSession->selection.windowHandle); + pSession->sharingData.transform = WL_OUTPUT_TRANSFORM_NORMAL; + } else { + Debug::log(ERR, "[screencopy] Unsupported selection {}", (int)pSession->selection.type); + return; + } + + pSession->sharingData.status = FRAME_QUEUED; + + if (pSession->sharingData.frameCallback) + zwlr_screencopy_frame_v1_add_listener(pSession->sharingData.frameCallback, &wlrFrameListener, pSession); + else if (pSession->sharingData.windowFrameCallback) + hyprland_toplevel_export_frame_v1_add_listener(pSession->sharingData.windowFrameCallback, &hyprlandFrameListener, pSession); + + Debug::log(LOG, "[screencopy] frame callbacks initialized"); +} + +void CScreencopyPortal::queueNextShareFrame(CScreencopyPortal::SSession* pSession) { + const auto PSTREAM = m_pPipewire->streamFromSession(pSession); + + if (PSTREAM && !PSTREAM->streamState) + return; + + g_pPortalManager->m_vTimers.emplace_back( + std::make_unique(1000.0 / pSession->sharingData.framerate, [pSession]() { g_pPortalManager->m_sPortals.screencopy->startFrameCopy(pSession); })); +} +bool CScreencopyPortal::hasToplevelCapabilities() { + return m_sState.toplevel; +} + +CScreencopyPortal::SSession* CScreencopyPortal::getSession(sdbus::ObjectPath& path) { + for (auto& s : m_vSessions) { + if (s->sessionHandle == path) + return s.get(); + } + + return nullptr; +} + +CScreencopyPortal::CScreencopyPortal(zwlr_screencopy_manager_v1* mgr) { + m_pObject = sdbus::createObject(*g_pPortalManager->getConnection(), OBJECT_PATH); + + m_pObject->registerMethod(INTERFACE_NAME, "CreateSession", "oosa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onCreateSession(c); }); + m_pObject->registerMethod(INTERFACE_NAME, "SelectSources", "oosa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onSelectSources(c); }); + m_pObject->registerMethod(INTERFACE_NAME, "Start", "oossa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onStart(c); }); + m_pObject->registerProperty(INTERFACE_NAME, "AvailableSourceTypes", "u", [](sdbus::PropertyGetReply& reply) -> void { reply << (uint32_t)(VIRTUAL | MONITOR | WINDOW); }); + m_pObject->registerProperty(INTERFACE_NAME, "AvailableCursorModes", "u", [](sdbus::PropertyGetReply& reply) -> void { reply << (uint32_t)(HIDDEN | EMBEDDED); }); + m_pObject->registerProperty(INTERFACE_NAME, "version", "u", [](sdbus::PropertyGetReply& reply) -> void { reply << (uint32_t)(3); }); + + m_pObject->finishRegistration(); + + m_sState.screencopy = mgr; + m_pPipewire = std::make_unique(); + + Debug::log(LOG, "[screencopy] init successful"); +} + +void CScreencopyPortal::appendToplevelExport(void* proto) { + m_sState.toplevel = (hyprland_toplevel_export_manager_v1*)proto; + + Debug::log(LOG, "[screencopy] Registered for toplevel export"); +} + +bool CPipewireConnection::good() { + return m_pContext && m_pCore; +} + +CPipewireConnection::CPipewireConnection() { + m_pContext = pw_context_new(g_pPortalManager->m_sPipewire.loop, nullptr, 0); + + if (!m_pContext) { + Debug::log(ERR, "[pipewire] pw didn't allow for a context"); + return; + } + + m_pCore = pw_context_connect(m_pContext, nullptr, 0); + + if (!m_pCore) { + Debug::log(ERR, "[pipewire] pw didn't allow for a context connection"); + return; + } + + Debug::log(LOG, "[pipewire] connected"); +} + +CPipewireConnection::~CPipewireConnection() { + if (m_pCore) + pw_core_disconnect(m_pCore); + if (m_pContext) + pw_context_destroy(m_pContext); +} + +// --------------- Pipewire Stream Handlers --------------- // + +static void pwStreamStateChange(void* data, pw_stream_state old, pw_stream_state state, const char* error) { + const auto PSTREAM = (CPipewireConnection::SPWStream*)data; + + PSTREAM->pSession->sharingData.nodeID = pw_stream_get_node_id(PSTREAM->stream); + + Debug::log(TRACE, "[pw] pwStreamStateChange on {} from {} to {}, node id {}", (void*)PSTREAM, pw_stream_state_as_string(old), pw_stream_state_as_string(state), + PSTREAM->pSession->sharingData.nodeID); + + switch (state) { + case PW_STREAM_STATE_STREAMING: + PSTREAM->streamState = true; + if (PSTREAM->pSession->sharingData.status == FRAME_NONE) + g_pPortalManager->m_sPortals.screencopy->startFrameCopy(PSTREAM->pSession); + break; + default: PSTREAM->streamState = false; break; + } + + if (state == PW_STREAM_STATE_UNCONNECTED) + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->destroyStream(PSTREAM->pSession); +} + +static void pwStreamParamChanged(void* data, uint32_t id, const spa_pod* param) { + const auto PSTREAM = (CPipewireConnection::SPWStream*)data; + + Debug::log(TRACE, "[pw] pwStreamParamChanged on {}", (void*)PSTREAM); + + if (id != SPA_PARAM_Format || !param) { + Debug::log(TRACE, "[pw] invalid call in pwStreamParamChanged"); + return; + } + + spa_pod_dynamic_builder dynBuilder[3]; + const spa_pod* params[4]; + uint8_t params_buffer[3][1024]; + + spa_pod_dynamic_builder_init(&dynBuilder[0], params_buffer[0], sizeof(params_buffer[0]), 2048); + spa_pod_dynamic_builder_init(&dynBuilder[1], params_buffer[1], sizeof(params_buffer[1]), 2048); + spa_pod_dynamic_builder_init(&dynBuilder[2], params_buffer[2], sizeof(params_buffer[2]), 2048); + + spa_format_video_raw_parse(param, &PSTREAM->pwVideoInfo); + PSTREAM->pSession->sharingData.framerate = PSTREAM->pwVideoInfo.max_framerate.num / PSTREAM->pwVideoInfo.max_framerate.denom; + + uint32_t data_type = 1 << SPA_DATA_MemFd; + + const struct spa_pod_prop* prop_modifier; + if ((prop_modifier = spa_pod_find_prop(param, nullptr, SPA_FORMAT_VIDEO_modifier))) { + Debug::log(TRACE, "[pipewire] pw requested dmabuf"); + PSTREAM->isDMA = true; + data_type = 1 << SPA_DATA_DmaBuf; + + RASSERT(PSTREAM->pwVideoInfo.format == pwFromDrmFourcc(PSTREAM->pSession->sharingData.frameInfoDMA.fmt), "invalid format in dma pw param change"); + + if ((prop_modifier->flags & SPA_POD_PROP_FLAG_DONT_FIXATE) > 0) { + Debug::log(TRACE, "[pw] don't fixate"); + const spa_pod* pod_modifier = &prop_modifier->value; + + uint32_t n_modifiers = SPA_POD_CHOICE_N_VALUES(pod_modifier) - 1; + uint64_t* modifiers = SPA_POD_CHOICE_VALUES(pod_modifier); + modifiers++; + uint32_t flags = GBM_BO_USE_RENDERING; + uint64_t modifier; + uint32_t n_params; + spa_pod_builder* builder[2] = {&dynBuilder[0].b, &dynBuilder[1].b}; + + gbm_bo* bo = + gbm_bo_create_with_modifiers2(g_pPortalManager->m_sWaylandConnection.gbmDevice, PSTREAM->pSession->sharingData.frameInfoDMA.w, + PSTREAM->pSession->sharingData.frameInfoDMA.h, PSTREAM->pSession->sharingData.frameInfoDMA.fmt, modifiers, n_modifiers, flags); + if (bo) { + modifier = gbm_bo_get_modifier(bo); + gbm_bo_destroy(bo); + goto fixate_format; + } + + Debug::log(TRACE, "[pw] unable to allocate a dmabuf with modifiers. Falling back to the old api"); + for (uint32_t i = 0; i < n_modifiers; i++) { + switch (modifiers[i]) { + case DRM_FORMAT_MOD_INVALID: + flags = + GBM_BO_USE_RENDERING; // ;cast->ctx->state->config->screencast_conf.force_mod_linear ? GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR : GBM_BO_USE_RENDERING; + break; + case DRM_FORMAT_MOD_LINEAR: flags = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR; break; + default: continue; + } + bo = gbm_bo_create(g_pPortalManager->m_sWaylandConnection.gbmDevice, PSTREAM->pSession->sharingData.frameInfoDMA.w, PSTREAM->pSession->sharingData.frameInfoDMA.h, + PSTREAM->pSession->sharingData.frameInfoDMA.fmt, flags); + if (bo) { + modifier = gbm_bo_get_modifier(bo); + gbm_bo_destroy(bo); + goto fixate_format; + } + } + + Debug::log(ERR, "[pw] failed to alloc dma"); + return; + + fixate_format: + params[0] = fixate_format(&dynBuilder[2].b, pwFromDrmFourcc(PSTREAM->pSession->sharingData.frameInfoDMA.fmt), PSTREAM->pSession->sharingData.frameInfoDMA.w, + PSTREAM->pSession->sharingData.frameInfoDMA.h, PSTREAM->pSession->sharingData.framerate, &modifier); + + n_params = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->buildFormatsFor(builder, ¶ms[1], PSTREAM); + n_params++; + + pw_stream_update_params(PSTREAM->stream, params, n_params); + spa_pod_dynamic_builder_clean(&dynBuilder[0]); + spa_pod_dynamic_builder_clean(&dynBuilder[1]); + spa_pod_dynamic_builder_clean(&dynBuilder[2]); + + Debug::log(TRACE, "[pw] Format fixated:"); + Debug::log(TRACE, "[pw] | buffer_type {}", "DMA (No fixate)"); + Debug::log(TRACE, "[pw] | format: {}", (int)PSTREAM->pwVideoInfo.format); + Debug::log(TRACE, "[pw] | modifier: {}", PSTREAM->pwVideoInfo.modifier); + Debug::log(TRACE, "[pw] | size: {}x{}", PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height); + Debug::log(TRACE, "[pw] | framerate {}", PSTREAM->pSession->sharingData.framerate); + + return; + } + } + + Debug::log(TRACE, "[pw] Format renegotiated:"); + Debug::log(TRACE, "[pw] | buffer_type {}", PSTREAM->isDMA ? "DMA" : "SHM"); + Debug::log(TRACE, "[pw] | format: {}", (int)PSTREAM->pwVideoInfo.format); + Debug::log(TRACE, "[pw] | modifier: {}", PSTREAM->pwVideoInfo.modifier); + Debug::log(TRACE, "[pw] | size: {}x{}", PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height); + Debug::log(TRACE, "[pw] | framerate {}", PSTREAM->pSession->sharingData.framerate); + + uint32_t blocks = 1; + + params[0] = build_buffer(&dynBuilder[0].b, blocks, PSTREAM->pSession->sharingData.frameInfoSHM.size, PSTREAM->pSession->sharingData.frameInfoSHM.stride, data_type); + + params[1] = spa_pod_builder_add_object(&dynBuilder[1].b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, + SPA_POD_Int(sizeof(struct spa_meta_header))); + + params[2] = spa_pod_builder_add_object(&dynBuilder[1].b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform))); + + params[3] = spa_pod_builder_add_object(&dynBuilder[2].b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, + SPA_POD_CHOICE_RANGE_Int(sizeof(struct spa_meta_region) * 4, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * 4)); + + pw_stream_update_params(PSTREAM->stream, params, 4); + spa_pod_dynamic_builder_clean(&dynBuilder[0]); + spa_pod_dynamic_builder_clean(&dynBuilder[1]); + spa_pod_dynamic_builder_clean(&dynBuilder[2]); +} + +static void pwStreamAddBuffer(void* data, pw_buffer* buffer) { + const auto PSTREAM = (CPipewireConnection::SPWStream*)data; + + Debug::log(TRACE, "[pw] pwStreamAddBuffer with {} on {}", (void*)buffer, (void*)PSTREAM); + + spa_data* spaData = buffer->buffer->datas; + spa_data_type type; + + if ((spaData[0].type & (1u << SPA_DATA_MemFd)) > 0) { + type = SPA_DATA_MemFd; + Debug::log(WARN, "[pipewire] Asked for a wl_shm buffer which is legacy."); + } else if ((spaData[0].type & (1u << SPA_DATA_DmaBuf)) > 0) { + type = SPA_DATA_DmaBuf; + } else { + Debug::log(ERR, "[pipewire] wrong format in addbuffer"); + return; + } + + const auto PBUFFER = PSTREAM->buffers.emplace_back(g_pPortalManager->m_sPortals.screencopy->m_pPipewire->createBuffer(PSTREAM, type == SPA_DATA_DmaBuf)).get(); + + PBUFFER->pwBuffer = buffer; + buffer->user_data = PBUFFER; + + Debug::log(TRACE, "[pw] buffer datas {}", buffer->buffer->n_datas); + + for (uint32_t plane = 0; plane < buffer->buffer->n_datas; plane++) { + spaData[plane].type = type; + spaData[plane].maxsize = PBUFFER->size[plane]; + spaData[plane].mapoffset = 0; + spaData[plane].chunk->size = PBUFFER->size[plane]; + spaData[plane].chunk->stride = PBUFFER->stride[plane]; + spaData[plane].chunk->offset = PBUFFER->offset[plane]; + spaData[plane].flags = 0; + spaData[plane].fd = PBUFFER->fd[plane]; + spaData[plane].data = NULL; + // clients have implemented to check chunk->size if the buffer is valid instead + // of using the flags. Until they are patched we should use some arbitrary value. + if (PBUFFER->isDMABUF && spaData[plane].chunk->size == 0) { + spaData[plane].chunk->size = 9; // This was choosen by a fair d20. + } + } +} + +static void pwStreamRemoveBuffer(void* data, pw_buffer* buffer) { + const auto PSTREAM = (CPipewireConnection::SPWStream*)data; + const auto PBUFFER = (SBuffer*)buffer->user_data; + + Debug::log(TRACE, "[pw] pwStreamRemoveBuffer with {} on {}", (void*)buffer, (void*)PSTREAM); + + if (!PBUFFER) + return; + + if (PSTREAM->currentPWBuffer == PBUFFER) + PSTREAM->currentPWBuffer = nullptr; + + wl_buffer_destroy(PBUFFER->wlBuffer); + for (int plane = 0; plane < PBUFFER->planeCount; plane++) { + close(PBUFFER->fd[plane]); + } + + for (uint32_t plane = 0; plane < buffer->buffer->n_datas; plane++) { + buffer->buffer->datas[plane].fd = -1; + } + + std::erase_if(PSTREAM->buffers, [&](const auto& other) { return other.get() == PBUFFER; }); + + buffer->user_data = nullptr; +} + +static const pw_stream_events pwStreamEvents = { + .version = PW_VERSION_STREAM_EVENTS, + .state_changed = pwStreamStateChange, + .param_changed = pwStreamParamChanged, + .add_buffer = pwStreamAddBuffer, + .remove_buffer = pwStreamRemoveBuffer, +}; + +// ------------------------------------------------------- // + +void CPipewireConnection::createStream(CScreencopyPortal::SSession* pSession) { + const auto PSTREAM = m_vStreams.emplace_back(std::make_unique(pSession)).get(); + + pw_loop_enter(g_pPortalManager->m_sPipewire.loop); + + uint8_t buffer[2][1024]; + spa_pod_dynamic_builder dynBuilder[2]; + spa_pod_dynamic_builder_init(&dynBuilder[0], buffer[0], sizeof(buffer[0]), 2048); + spa_pod_dynamic_builder_init(&dynBuilder[1], buffer[1], sizeof(buffer[1]), 2048); + + const std::string NAME = getRandName("xdph-streaming-"); + + PSTREAM->stream = pw_stream_new(m_pCore, NAME.c_str(), pw_properties_new(PW_KEY_MEDIA_CLASS, "Video/Source", nullptr)); + + Debug::log(TRACE, "[pw] New stream name {}", NAME); + + if (!PSTREAM->stream) { + Debug::log(ERR, "[pipewire] refused to create stream"); + exit(1); + } + + spa_pod_builder* builder[2] = {&dynBuilder[0].b, &dynBuilder[1].b}; + const spa_pod* params[2]; + const auto PARAMCOUNT = buildFormatsFor(builder, params, PSTREAM); + + spa_pod_dynamic_builder_clean(&dynBuilder[0]); + spa_pod_dynamic_builder_clean(&dynBuilder[1]); + + pw_stream_add_listener(PSTREAM->stream, &PSTREAM->streamListener, &pwStreamEvents, PSTREAM); + + pw_stream_connect(PSTREAM->stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, (pw_stream_flags)(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS), params, PARAMCOUNT); + + pSession->sharingData.nodeID = pw_stream_get_node_id(PSTREAM->stream); + + Debug::log(TRACE, "[pw] Stream got nodeid {}", pSession->sharingData.nodeID); +} + +void CPipewireConnection::destroyStream(CScreencopyPortal::SSession* pSession) { + const auto PSTREAM = streamFromSession(pSession); + + if (!PSTREAM || !PSTREAM->stream) + return; + + if (!PSTREAM->buffers.empty()) { + for (auto& b : PSTREAM->buffers) { + pwStreamRemoveBuffer(PSTREAM, b->pwBuffer); + } + } + + pw_stream_flush(PSTREAM->stream, false); + pw_stream_disconnect(PSTREAM->stream); + pw_stream_destroy(PSTREAM->stream); + + pSession->sharingData.active = false; + + std::erase_if(m_vStreams, [&](const auto& other) { return other.get() == PSTREAM; }); +} + +static bool wlr_query_dmabuf_modifiers(uint32_t drm_format, uint32_t num_modifiers, uint64_t* modifiers, uint32_t* max_modifiers) { + if (g_pPortalManager->m_vDMABUFMods.empty()) + return false; + + if (num_modifiers == 0) { + *max_modifiers = 0; + for (auto& mod : g_pPortalManager->m_vDMABUFMods) { + if (mod.fourcc == drm_format && + (mod.mod == DRM_FORMAT_MOD_INVALID || gbm_device_get_format_modifier_plane_count(g_pPortalManager->m_sWaylandConnection.gbmDevice, mod.fourcc, mod.mod) > 0)) + (*max_modifiers)++; + } + return true; + } + + for (size_t i = 0; i < g_pPortalManager->m_vDMABUFMods.size(); ++i) { + if (i >= num_modifiers) + break; + + const auto& mod = g_pPortalManager->m_vDMABUFMods[i]; + + if (mod.fourcc == drm_format && + (mod.mod == DRM_FORMAT_MOD_INVALID || gbm_device_get_format_modifier_plane_count(g_pPortalManager->m_sWaylandConnection.gbmDevice, mod.fourcc, mod.mod) > 0)) + modifiers[i] = mod.mod; + } + + *max_modifiers = num_modifiers; + return true; +} + +static bool build_modifierlist(CPipewireConnection::SPWStream* stream, uint32_t drm_format, uint64_t** modifiers, uint32_t* modifier_count) { + if (!wlr_query_dmabuf_modifiers(drm_format, 0, nullptr, modifier_count)) { + *modifiers = NULL; + *modifier_count = 0; + return false; + } + if (*modifier_count == 0) { + Debug::log(ERR, "[pw] build_modifierlist: no mods"); + *modifiers = NULL; + return true; + } + *modifiers = (uint64_t*)calloc(*modifier_count, sizeof(uint64_t)); + bool ret = wlr_query_dmabuf_modifiers(drm_format, *modifier_count, *modifiers, modifier_count); + Debug::log(TRACE, "[pw] build_modifierlist: count {}", *modifier_count); + return ret; +} + +uint32_t CPipewireConnection::buildFormatsFor(spa_pod_builder* b[2], const spa_pod* params[2], CPipewireConnection::SPWStream* stream) { + uint32_t paramCount = 0; + uint32_t modCount = 0; + uint64_t* modifiers = nullptr; + + if (build_modifierlist(stream, stream->pSession->sharingData.frameInfoDMA.fmt, &modifiers, &modCount) && modCount > 0) { + Debug::log(LOG, "[pw] Building modifiers for dma"); + + 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); + 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); + assert(params[1] != NULL); + } else { + Debug::log(LOG, "[pw] Building modifiers for shm"); + + 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); + } + + return paramCount; +} + +bool CPipewireConnection::buildModListFor(CPipewireConnection::SPWStream* stream, uint32_t drmFmt, uint64_t** mods, uint32_t* modCount) { + return true; +} + +CPipewireConnection::SPWStream* CPipewireConnection::streamFromSession(CScreencopyPortal::SSession* pSession) { + for (auto& s : m_vStreams) { + if (s->pSession == pSession) + return s.get(); + } + return nullptr; +} + +void CPipewireConnection::enqueue(CScreencopyPortal::SSession* pSession) { + const auto PSTREAM = streamFromSession(pSession); + + Debug::log(TRACE, "[pw] enqueue on {}", (void*)PSTREAM); + + if (!PSTREAM->currentPWBuffer) { + Debug::log(ERR, "[pipewire] no buffer in enqueue"); + return; + } + + spa_buffer* spaBuf = PSTREAM->currentPWBuffer->pwBuffer->buffer; + const bool CORRUPT = PSTREAM->pSession->sharingData.status != FRAME_READY; + if (CORRUPT) + Debug::log(TRACE, "[pw] buffer corrupt"); + + Debug::log(TRACE, "[pw] Enqueue data:"); + + spa_meta_header* header = (spa_meta_header*)spa_buffer_find_meta_data(spaBuf, SPA_META_Header, sizeof(*header)); + if (header) { + header->pts = PSTREAM->pSession->sharingData.tvTimestampNs; + header->flags = CORRUPT ? SPA_META_HEADER_FLAG_CORRUPTED : 0; + header->seq = PSTREAM->seq++; + header->dts_offset = 0; + Debug::log(TRACE, "[pw] | seq {}", header->seq); + Debug::log(TRACE, "[pw] | pts {}", header->pts); + } + + spa_meta_videotransform* vt = (spa_meta_videotransform*)spa_buffer_find_meta_data(spaBuf, SPA_META_VideoTransform, sizeof(*vt)); + if (vt) { + vt->transform = pSession->sharingData.transform; + Debug::log(TRACE, "[pw] | meta transform {}", vt->transform); + } + + spa_meta* damage = spa_buffer_find_meta(spaBuf, SPA_META_VideoDamage); + if (damage) { + Debug::log(TRACE, "[pw] | meta has damage"); + + spa_region* damageRegion = (spa_region*)spa_meta_first(damage); + uint32_t damageCounter = 0; + do { + if (damageCounter >= pSession->sharingData.damageCount) { + *damageRegion = SPA_REGION(0, 0, 0, 0); + Debug::log(TRACE, "[pw] | end damage @ {}: {} {} {} {}", damageCounter, damageRegion->position.x, damageRegion->position.y, damageRegion->size.width, + damageRegion->size.height); + break; + } + + *damageRegion = SPA_REGION(pSession->sharingData.damage[damageCounter].x, pSession->sharingData.damage[damageCounter].y, pSession->sharingData.damage[damageCounter].w, + pSession->sharingData.damage[damageCounter].h); + Debug::log(TRACE, "[pw] | damage @ {}: {} {} {} {}", damageCounter, damageRegion->position.x, damageRegion->position.y, damageRegion->size.width, + damageRegion->size.height); + damageCounter++; + } while (spa_meta_check(damageRegion + 1, damage) && damageRegion++); + + if (damageCounter < pSession->sharingData.damageCount) { + // TODO: merge damage properly + *damageRegion = SPA_REGION(0, 0, pSession->sharingData.frameInfoDMA.w, pSession->sharingData.frameInfoDMA.h); + Debug::log(TRACE, "[pw] | damage overflow, damaged whole"); + } + } + + spa_data* datas = spaBuf->datas; + + Debug::log(TRACE, "[pw] | size {}x{}", PSTREAM->pSession->sharingData.frameInfoDMA.w, PSTREAM->pSession->sharingData.frameInfoDMA.h); + + for (uint32_t plane = 0; plane < spaBuf->n_datas; plane++) { + datas[plane].chunk->flags = CORRUPT ? SPA_CHUNK_FLAG_CORRUPTED : SPA_CHUNK_FLAG_NONE; + + Debug::log(TRACE, "[pw] | plane {}", plane); + Debug::log(TRACE, "[pw] | fd {}", datas[plane].fd); + Debug::log(TRACE, "[pw] | maxsize {}", datas[plane].maxsize); + Debug::log(TRACE, "[pw] | size {}", datas[plane].chunk->size); + Debug::log(TRACE, "[pw] | stride {}", datas[plane].chunk->stride); + Debug::log(TRACE, "[pw] | offset {}", datas[plane].chunk->offset); + Debug::log(TRACE, "[pw] | flags {}", datas[plane].chunk->flags); + } + + Debug::log(TRACE, "[pw] --------------------------------- End enqueue"); + + pw_stream_queue_buffer(PSTREAM->stream, PSTREAM->currentPWBuffer->pwBuffer); + + PSTREAM->currentPWBuffer = nullptr; +} + +void CPipewireConnection::dequeue(CScreencopyPortal::SSession* pSession) { + const auto PSTREAM = streamFromSession(pSession); + + Debug::log(TRACE, "[pw] dequeue on {}", (void*)PSTREAM); + + const auto PWBUF = pw_stream_dequeue_buffer(PSTREAM->stream); + + if (!PWBUF) { + Debug::log(TRACE, "[pw] dequeue failed"); + PSTREAM->currentPWBuffer = nullptr; + return; + } + + const auto PBUF = (SBuffer*)PWBUF->user_data; + + PSTREAM->currentPWBuffer = PBUF; +} + +std::unique_ptr CPipewireConnection::createBuffer(CPipewireConnection::SPWStream* pStream, bool dmabuf) { + std::unique_ptr pBuffer = std::make_unique(); + + pBuffer->isDMABUF = dmabuf; + + Debug::log(TRACE, "[pw] createBuffer: type {}", dmabuf ? "dma" : "shm"); + + if (dmabuf) { + pBuffer->w = pStream->pSession->sharingData.frameInfoDMA.w; + pBuffer->h = pStream->pSession->sharingData.frameInfoDMA.h; + pBuffer->fmt = pStream->pSession->sharingData.frameInfoDMA.fmt; + + uint32_t flags = GBM_BO_USE_RENDERING; + + if (pStream->pwVideoInfo.modifier != DRM_FORMAT_MOD_INVALID) { + uint64_t* mods = (uint64_t*)&pStream->pwVideoInfo.modifier; + pBuffer->bo = gbm_bo_create_with_modifiers2(g_pPortalManager->m_sWaylandConnection.gbmDevice, pBuffer->w, pBuffer->h, pBuffer->fmt, mods, 1, flags); + } else { + pBuffer->bo = gbm_bo_create(g_pPortalManager->m_sWaylandConnection.gbmDevice, pBuffer->w, pBuffer->h, pBuffer->fmt, flags); + } + + if (!pBuffer->bo) { + Debug::log(ERR, "[pw] Couldn't create a drm buffer"); + return nullptr; + } + + pBuffer->planeCount = gbm_bo_get_plane_count(pBuffer->bo); + + zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params((zwp_linux_dmabuf_v1*)g_pPortalManager->m_sWaylandConnection.linuxDmabuf); + if (!params) { + Debug::log(ERR, "[pw] zwp_linux_dmabuf_v1_create_params failed"); + gbm_bo_destroy(pBuffer->bo); + return nullptr; + } + + for (size_t plane = 0; plane < (size_t)pBuffer->planeCount; plane++) { + pBuffer->size[plane] = 0; + pBuffer->stride[plane] = gbm_bo_get_stride_for_plane(pBuffer->bo, plane); + pBuffer->offset[plane] = gbm_bo_get_offset(pBuffer->bo, plane); + uint64_t mod = gbm_bo_get_modifier(pBuffer->bo); + pBuffer->fd[plane] = gbm_bo_get_fd_for_plane(pBuffer->bo, plane); + + if (pBuffer->fd[plane] < 0) { + Debug::log(ERR, "[pw] gbm_bo_get_fd_for_plane failed"); + zwp_linux_buffer_params_v1_destroy(params); + gbm_bo_destroy(pBuffer->bo); + for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) { + close(pBuffer->fd[plane_tmp]); + } + return NULL; + } + + zwp_linux_buffer_params_v1_add(params, pBuffer->fd[plane], plane, pBuffer->offset[plane], pBuffer->stride[plane], mod >> 32, mod & 0xffffffff); + } + + pBuffer->wlBuffer = zwp_linux_buffer_params_v1_create_immed(params, pBuffer->w, pBuffer->h, pBuffer->fmt, /* flags */ 0); + zwp_linux_buffer_params_v1_destroy(params); + + if (!pBuffer->wlBuffer) { + Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed"); + gbm_bo_destroy(pBuffer->bo); + for (size_t plane = 0; plane < (size_t)pBuffer->planeCount; plane++) { + close(pBuffer->fd[plane]); + } + + return nullptr; + } + } else { + + pBuffer->w = pStream->pSession->sharingData.frameInfoSHM.w; + pBuffer->h = pStream->pSession->sharingData.frameInfoSHM.h; + pBuffer->fmt = pStream->pSession->sharingData.frameInfoSHM.fmt; + + pBuffer->planeCount = 1; + pBuffer->size[0] = pStream->pSession->sharingData.frameInfoSHM.size; + pBuffer->stride[0] = pStream->pSession->sharingData.frameInfoSHM.stride; + pBuffer->offset[0] = 0; + pBuffer->fd[0] = anonymous_shm_open(); + + if (pBuffer->fd[0] == -1) { + Debug::log(ERR, "[screencopy] anonymous_shm_open failed"); + return nullptr; + } + + if (ftruncate(pBuffer->fd[0], pBuffer->size[0]) < 0) { + Debug::log(ERR, "[screencopy] ftruncate failed"); + return nullptr; + } + + pBuffer->wlBuffer = import_wl_shm_buffer(pBuffer->fd[0], wlSHMFromDrmFourcc(pStream->pSession->sharingData.frameInfoSHM.fmt), pStream->pSession->sharingData.frameInfoSHM.w, + pStream->pSession->sharingData.frameInfoSHM.h, pStream->pSession->sharingData.frameInfoSHM.stride); + if (!pBuffer->wlBuffer) { + Debug::log(ERR, "[screencopy] import_wl_shm_buffer failed"); + return nullptr; + } + } + + return pBuffer; +} + +void CPipewireConnection::updateStreamParam(SPWStream* pStream) { + Debug::log(TRACE, "[pw] update stream params"); + + uint8_t paramsBuf[2][1024]; + spa_pod_dynamic_builder dynBuilder[2]; + spa_pod_dynamic_builder_init(&dynBuilder[0], paramsBuf[0], sizeof(paramsBuf[0]), 2048); + spa_pod_dynamic_builder_init(&dynBuilder[1], paramsBuf[1], sizeof(paramsBuf[1]), 2048); + const spa_pod* params[2]; + + spa_pod_builder* builder[2] = {&dynBuilder[0].b, &dynBuilder[1].b}; + uint32_t n_params = buildFormatsFor(builder, params, pStream); + + pw_stream_update_params(pStream->stream, params, n_params); + spa_pod_dynamic_builder_clean(&dynBuilder[0]); + spa_pod_dynamic_builder_clean(&dynBuilder[1]); +} diff --git a/src/portals/Screencopy.hpp b/src/portals/Screencopy.hpp new file mode 100644 index 0000000..78d49e3 --- /dev/null +++ b/src/portals/Screencopy.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include +#include +#include +#include "../shared/ScreencopyShared.hpp" +#include +#include "../shared/Session.hpp" + +enum cursorModes +{ + HIDDEN = 1, + EMBEDDED = 2, + METADATA = 4, +}; + +enum sourceTypes +{ + MONITOR = 1, + WINDOW = 2, + VIRTUAL = 4, +}; + +enum frameStatus +{ + FRAME_NONE = 0, + FRAME_QUEUED, + FRAME_READY, + FRAME_FAILED, + FRAME_RENEG, +}; + +struct pw_context; +struct pw_core; +struct pw_stream; +struct pw_buffer; + +struct SBuffer { + bool isDMABUF = false; + uint32_t w = 0, h = 0, fmt = 0; + int planeCount = 0; + + int fd[4]; + uint32_t size[4], stride[4], offset[4]; + + gbm_bo* bo = nullptr; + + wl_buffer* wlBuffer = nullptr; + pw_buffer* pwBuffer = nullptr; +}; + +class CPipewireConnection; + +class CScreencopyPortal { + public: + CScreencopyPortal(zwlr_screencopy_manager_v1*); + + void appendToplevelExport(void*); + + void onCreateSession(sdbus::MethodCall& call); + void onSelectSources(sdbus::MethodCall& call); + void onStart(sdbus::MethodCall& call); + + struct SSession { + std::string appid; + sdbus::ObjectPath requestHandle, sessionHandle; + uint32_t cursorMode = HIDDEN; + uint32_t persistMode = 0; + + std::unique_ptr request; + std::unique_ptr session; + SSelectionData selection; + + struct { + bool active = false; + zwlr_screencopy_frame_v1* frameCallback = nullptr; + hyprland_toplevel_export_frame_v1* windowFrameCallback = nullptr; + frameStatus status = FRAME_NONE; + uint64_t tvSec = 0; + uint32_t tvNsec = 0; + uint64_t tvTimestampNs = 0; + uint32_t nodeID = 0; + uint32_t framerate = 60; + wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + + struct { + uint32_t w = 0, h = 0, size = 0, stride = 0, fmt = 0; + } frameInfoSHM; + + struct { + uint32_t w = 0, h = 0, fmt = 0; + } frameInfoDMA; + + struct { + uint32_t x = 0, y = 0, w = 0, h = 0; + } damage[4]; + uint32_t damageCount = 0; + } sharingData; + + void onCloseRequest(sdbus::MethodCall&); + void onCloseSession(sdbus::MethodCall&); + }; + + void startFrameCopy(SSession* pSession); + void queueNextShareFrame(SSession* pSession); + bool hasToplevelCapabilities(); + + std::unique_ptr m_pPipewire; + + private: + std::unique_ptr m_pObject; + + std::vector> m_vSessions; + + SSession* getSession(sdbus::ObjectPath& path); + void startSharing(SSession* pSession); + + struct { + zwlr_screencopy_manager_v1* screencopy = nullptr; + hyprland_toplevel_export_manager_v1* toplevel = nullptr; + } m_sState; + + const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.ScreenCast"; + const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop"; +}; + +class CPipewireConnection { + public: + CPipewireConnection(); + ~CPipewireConnection(); + + bool good(); + + void createStream(CScreencopyPortal::SSession* pSession); + void destroyStream(CScreencopyPortal::SSession* pSession); + + void enqueue(CScreencopyPortal::SSession* pSession); + void dequeue(CScreencopyPortal::SSession* pSession); + + struct SPWStream { + CScreencopyPortal::SSession* pSession = nullptr; + pw_stream* stream = nullptr; + bool streamState = false; + spa_hook streamListener; + SBuffer* currentPWBuffer = nullptr; + spa_video_info_raw pwVideoInfo; + uint32_t seq = 0; + bool isDMA = false; + + std::vector> buffers; + }; + + std::unique_ptr createBuffer(SPWStream* pStream, bool dmabuf); + SPWStream* streamFromSession(CScreencopyPortal::SSession* pSession); + uint32_t buildFormatsFor(spa_pod_builder* b[2], const spa_pod* params[2], SPWStream* stream); + void updateStreamParam(SPWStream* pStream); + + private: + std::vector> m_vStreams; + + bool buildModListFor(SPWStream* stream, uint32_t drmFmt, uint64_t** mods, uint32_t* modCount); + + pw_context* m_pContext = nullptr; + pw_core* m_pCore = nullptr; +}; \ No newline at end of file diff --git a/src/screencast/fps_limit.c b/src/screencast/fps_limit.c deleted file mode 100644 index f34c874..0000000 --- a/src/screencast/fps_limit.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "fps_limit.h" -#include "logger.h" -#include "timespec_util.h" -#include -#include -#include -#include - -#define FPS_MEASURE_PERIOD_SEC 5.0 - -void measure_fps(struct fps_limit_state *state, struct timespec *now); - -void fps_limit_measure_start(struct fps_limit_state *state, double max_fps) { - if (max_fps <= 0.0) { - return; - } - - clock_gettime(CLOCK_MONOTONIC, &state->frame_last_time); -} - -uint64_t fps_limit_measure_end(struct fps_limit_state *state, double max_fps) { - if (max_fps <= 0.0) { - return 0; - } - - // `fps_limit_measure_start` was not called? - assert(!timespec_is_zero(&state->frame_last_time)); - - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - int64_t elapsed_ns = timespec_diff_ns(&now, &state->frame_last_time); - - measure_fps(state, &now); - - int64_t target_ns = (1.0 / max_fps) * TIMESPEC_NSEC_PER_SEC; - int64_t delay_ns = target_ns - elapsed_ns; - if (delay_ns > 0) { - logprint(TRACE, "fps_limit: elapsed time since the last measurement: %u, " - "target %u, should delay for %u (ns)", elapsed_ns, target_ns, delay_ns); - return delay_ns; - } else { - logprint(TRACE, "fps_limit: elapsed time since the last measurement: %u, " - "target %u, target not met (ns)", elapsed_ns, target_ns); - return 0; - } -} - -void measure_fps(struct fps_limit_state *state, struct timespec *now) { - if (timespec_is_zero(&state->fps_last_time)) { - state->fps_last_time = *now; - return; - } - - state->fps_frame_count++; - - int64_t elapsed_ns = timespec_diff_ns(now, &state->fps_last_time); - - double elapsed_sec = (double) elapsed_ns / (double) TIMESPEC_NSEC_PER_SEC; - if (elapsed_sec < FPS_MEASURE_PERIOD_SEC) { - return; - } - - double avg_frames_per_sec = state->fps_frame_count / elapsed_sec; - - logprint(DEBUG, "fps_limit: average FPS in the last %0.2f seconds: %0.2f", - elapsed_sec, avg_frames_per_sec); - - state->fps_last_time = *now; - state->fps_frame_count = 0; -} diff --git a/src/screencast/pipewire_screencast.c b/src/screencast/pipewire_screencast.c deleted file mode 100644 index 11a6f01..0000000 --- a/src/screencast/pipewire_screencast.c +++ /dev/null @@ -1,634 +0,0 @@ -#include "pipewire_screencast.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "logger.h" -#include "wlr_screencast.h" -#include "xdpw.h" - -static struct spa_pod *build_buffer(struct spa_pod_builder *b, uint32_t blocks, uint32_t size, - uint32_t stride, uint32_t datatype) { - assert(blocks > 0); - assert(datatype > 0); - struct spa_pod_frame f[1]; - - spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); - spa_pod_builder_add(b, SPA_PARAM_BUFFERS_buffers, - SPA_POD_CHOICE_RANGE_Int(XDPW_PWR_BUFFERS, XDPW_PWR_BUFFERS_MIN, 32), 0); - spa_pod_builder_add(b, SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), 0); - if (size > 0) { - spa_pod_builder_add(b, SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), 0); - } - if (stride > 0) { - spa_pod_builder_add(b, SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), 0); - } - spa_pod_builder_add(b, SPA_PARAM_BUFFERS_align, SPA_POD_Int(XDPW_PWR_ALIGN), 0); - spa_pod_builder_add(b, SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(datatype), 0); - return spa_pod_builder_pop(b, &f[0]); -} - -static struct spa_pod *fixate_format(struct spa_pod_builder *b, enum spa_video_format format, - uint32_t width, uint32_t height, uint32_t framerate, uint64_t *modifier) -{ - struct spa_pod_frame f[1]; - - enum spa_video_format format_without_alpha = xdpw_format_pw_strip_alpha(format); - - spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); - spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); - /* format */ - if (modifier || format_without_alpha == SPA_VIDEO_FORMAT_UNKNOWN) { - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); - } else { - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, - SPA_POD_CHOICE_ENUM_Id(3, format, format, format_without_alpha), 0); - } - /* modifiers */ - if (modifier) { - // implicit modifier - spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); - spa_pod_builder_long(b, *modifier); - } - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, - SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), - 0); - // variable framerate - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, - SPA_POD_Fraction(&SPA_FRACTION(0, 1)), 0); - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, - SPA_POD_CHOICE_RANGE_Fraction( - &SPA_FRACTION(framerate, 1), - &SPA_FRACTION(1, 1), - &SPA_FRACTION(framerate, 1)), - 0); - return spa_pod_builder_pop(b, &f[0]); -} - -static struct spa_pod *build_format(struct spa_pod_builder *b, enum spa_video_format format, - uint32_t width, uint32_t height, uint32_t framerate, - uint64_t *modifiers, int modifier_count) { - struct spa_pod_frame f[2]; - int i, c; - - enum spa_video_format format_without_alpha = xdpw_format_pw_strip_alpha(format); - - spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); - spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); - /* format */ - if (modifier_count > 0 || format_without_alpha == SPA_VIDEO_FORMAT_UNKNOWN) { - // modifiers are defined only in combinations with their format - // we should not announce the format without alpha - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); - } else { - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, - SPA_POD_CHOICE_ENUM_Id(3, format, format, format_without_alpha), 0); - } - /* modifiers */ - if (modifier_count > 0) { - // build an enumeration of modifiers - spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); - spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); - // modifiers from the array - for (i = 0, c = 0; i < modifier_count; i++) { - spa_pod_builder_long(b, modifiers[i]); - if (c++ == 0) - spa_pod_builder_long(b, modifiers[i]); - } - spa_pod_builder_pop(b, &f[1]); - } - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, - SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), - 0); - // variable framerate - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, - SPA_POD_Fraction(&SPA_FRACTION(0, 1)), 0); - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, - SPA_POD_CHOICE_RANGE_Fraction( - &SPA_FRACTION(framerate, 1), - &SPA_FRACTION(1, 1), - &SPA_FRACTION(framerate, 1)), - 0); - return spa_pod_builder_pop(b, &f[0]); -} - -static bool build_modifierlist(struct xdpw_screencast_instance *cast, - uint32_t drm_format, uint64_t **modifiers, uint32_t *modifier_count) { - if (!wlr_query_dmabuf_modifiers(cast->ctx, drm_format, 0, NULL, modifier_count)) { - *modifiers = NULL; - *modifier_count = 0; - return false; - } - if (*modifier_count == 0) { - logprint(INFO, "wlroots: no modifiers available for format %u", drm_format); - *modifiers = NULL; - return true; - } - *modifiers = calloc(*modifier_count, sizeof(uint64_t)); - bool ret = wlr_query_dmabuf_modifiers(cast->ctx, drm_format, *modifier_count, *modifiers, modifier_count); - logprint(INFO, "wlroots: num_modififiers %d", *modifier_count); - return ret; -} - -static uint32_t build_formats(struct spa_pod_builder *b[static 2], struct xdpw_screencast_instance *cast, - const struct spa_pod *params[static 2]) { - uint32_t param_count; - uint32_t modifier_count; - uint64_t *modifiers = NULL; - - if (!cast->avoid_dmabufs && - build_modifierlist(cast, cast->screencopy_frame_info[DMABUF].format, &modifiers, &modifier_count) && modifier_count > 0) { - param_count = 2; - params[0] = build_format(b[0], xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[DMABUF].format), - cast->screencopy_frame_info[DMABUF].width, cast->screencopy_frame_info[DMABUF].height, cast->framerate, - modifiers, modifier_count); - assert(params[0] != NULL); - params[1] = build_format(b[1], xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[WL_SHM].format), - cast->screencopy_frame_info[WL_SHM].width, cast->screencopy_frame_info[WL_SHM].height, cast->framerate, - NULL, 0); - assert(params[1] != NULL); - } else { - param_count = 1; - params[0] = build_format(b[0], xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[WL_SHM].format), - cast->screencopy_frame_info[WL_SHM].width, cast->screencopy_frame_info[WL_SHM].height, cast->framerate, - NULL, 0); - assert(params[0] != NULL); - } - free(modifiers); - return param_count; -} - -static void pwr_handle_stream_state_changed(void *data, - enum pw_stream_state old, enum pw_stream_state state, const char *error) { - struct xdpw_screencast_instance *cast = data; - cast->node_id = pw_stream_get_node_id(cast->stream); - - logprint(INFO, "pipewire: stream state changed to \"%s\"", - pw_stream_state_as_string(state)); - logprint(INFO, "pipewire: node id is %d", (int)cast->node_id); - - switch (state) { - case PW_STREAM_STATE_STREAMING: - cast->pwr_stream_state = true; - if (cast->frame_state == XDPW_FRAME_STATE_NONE) { - xdpw_wlr_frame_start(cast); - } - break; - case PW_STREAM_STATE_PAUSED: - if (old == PW_STREAM_STATE_STREAMING) { - xdpw_pwr_enqueue_buffer(cast); - } - // fall through - default: - cast->pwr_stream_state = false; - break; - } -} - -static void pwr_handle_stream_param_changed(void *data, uint32_t id, - const struct spa_pod *param) { - logprint(TRACE, "pipewire: stream parameters changed"); - struct xdpw_screencast_instance *cast = data; - struct pw_stream *stream = cast->stream; - uint8_t params_buffer[3][1024]; - struct spa_pod_dynamic_builder b[3]; - const struct spa_pod *params[4]; - uint32_t blocks; - uint32_t data_type; - - if (!param || id != SPA_PARAM_Format) { - return; - } - - spa_pod_dynamic_builder_init(&b[0], params_buffer[0], sizeof(params_buffer[0]), 2048); - spa_pod_dynamic_builder_init(&b[1], params_buffer[1], sizeof(params_buffer[1]), 2048); - spa_pod_dynamic_builder_init(&b[2], params_buffer[2], sizeof(params_buffer[2]), 2048); - - spa_format_video_raw_parse(param, &cast->pwr_format); - cast->framerate = (uint32_t)(cast->pwr_format.max_framerate.num / cast->pwr_format.max_framerate.denom); - - const struct spa_pod_prop *prop_modifier; - if ((prop_modifier = spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier)) != NULL) { - cast->buffer_type = DMABUF; - data_type = 1<pwr_format.format == xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[DMABUF].format)); - if ((prop_modifier->flags & SPA_POD_PROP_FLAG_DONT_FIXATE) > 0) { - const struct spa_pod *pod_modifier = &prop_modifier->value; - - uint32_t n_modifiers = SPA_POD_CHOICE_N_VALUES(pod_modifier) - 1; - uint64_t *modifiers = SPA_POD_CHOICE_VALUES(pod_modifier); - modifiers++; - uint32_t flags = GBM_BO_USE_RENDERING; - uint64_t modifier; - uint32_t n_params; - struct spa_pod_builder *builder[2] = {&b[0].b, &b[1].b}; - - struct gbm_bo *bo = gbm_bo_create_with_modifiers2(cast->ctx->gbm, - cast->screencopy_frame_info[cast->buffer_type].width, cast->screencopy_frame_info[cast->buffer_type].height, - cast->screencopy_frame_info[cast->buffer_type].format, modifiers, n_modifiers, flags); - if (bo) { - modifier = gbm_bo_get_modifier(bo); - gbm_bo_destroy(bo); - goto fixate_format; - } - - logprint(INFO, "pipewire: unable to allocate a dmabuf with modifiers. Falling back to the old api"); - for (uint32_t i = 0; i < n_modifiers; i++) { - switch (modifiers[i]) { - case DRM_FORMAT_MOD_INVALID: - flags = cast->ctx->state->config->screencast_conf.force_mod_linear ? - GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR : GBM_BO_USE_RENDERING; - break; - case DRM_FORMAT_MOD_LINEAR: - flags = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR; - break; - default: - continue; - } - bo = gbm_bo_create(cast->ctx->gbm, - cast->screencopy_frame_info[cast->buffer_type].width, cast->screencopy_frame_info[cast->buffer_type].height, - cast->screencopy_frame_info[cast->buffer_type].format, flags); - if (bo) { - modifier = gbm_bo_get_modifier(bo); - gbm_bo_destroy(bo); - goto fixate_format; - } - } - - logprint(WARN, "pipewire: unable to allocate a dmabuf. Falling back to shm"); - cast->avoid_dmabufs = true; - - n_params = build_formats(builder, cast, ¶ms[0]); - pw_stream_update_params(stream, params, n_params); - spa_pod_dynamic_builder_clean(&b[0]); - spa_pod_dynamic_builder_clean(&b[1]); - spa_pod_dynamic_builder_clean(&b[2]); - return; - -fixate_format: - params[0] = fixate_format(&b[2].b, xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[cast->buffer_type].format), - cast->screencopy_frame_info[cast->buffer_type].width, cast->screencopy_frame_info[cast->buffer_type].height, cast->framerate, &modifier); - - n_params = build_formats(builder, cast, ¶ms[1]); - n_params++; - - pw_stream_update_params(stream, params, n_params); - spa_pod_dynamic_builder_clean(&b[0]); - spa_pod_dynamic_builder_clean(&b[1]); - spa_pod_dynamic_builder_clean(&b[2]); - return; - } - - if (cast->pwr_format.modifier == DRM_FORMAT_MOD_INVALID) { - blocks = 1; - } else { - blocks = gbm_device_get_format_modifier_plane_count(cast->ctx->gbm, - cast->screencopy_frame_info[DMABUF].format, cast->pwr_format.modifier); - } - } else { - cast->buffer_type = WL_SHM; - blocks = 1; - data_type = 1<buffer_type, data_type); - logprint(DEBUG, "pipewire: format: %u", cast->pwr_format.format); - logprint(DEBUG, "pipewire: modifier: %lu", cast->pwr_format.modifier); - logprint(DEBUG, "pipewire: size: (%u, %u)", cast->pwr_format.size.width, cast->pwr_format.size.height); - logprint(DEBUG, "pipewire: max_framerate: (%u / %u)", cast->pwr_format.max_framerate.num, cast->pwr_format.max_framerate.denom); - - params[0] = build_buffer(&b[0].b, blocks, cast->screencopy_frame_info[cast->buffer_type].size, - cast->screencopy_frame_info[cast->buffer_type].stride, data_type); - - params[1] = spa_pod_builder_add_object(&b[1].b, - SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - - params[2] = spa_pod_builder_add_object(&b[1].b, - SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform))); - - params[3] = spa_pod_builder_add_object(&b[2].b, - SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), - SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int( - sizeof(struct spa_meta_region) * 4, - sizeof(struct spa_meta_region) * 1, - sizeof(struct spa_meta_region) * 4)); - - pw_stream_update_params(stream, params, 3); - spa_pod_dynamic_builder_clean(&b[0]); - spa_pod_dynamic_builder_clean(&b[1]); - spa_pod_dynamic_builder_clean(&b[2]); -} - -static void pwr_handle_stream_add_buffer(void *data, struct pw_buffer *buffer) { - struct xdpw_screencast_instance *cast = data; - struct spa_data *d; - enum spa_data_type t; - - logprint(DEBUG, "pipewire: add buffer event handle"); - - d = buffer->buffer->datas; - - // Select buffer type from negotiation result - if ((d[0].type & (1u << SPA_DATA_MemFd)) > 0) { - assert(cast->buffer_type == WL_SHM); - t = SPA_DATA_MemFd; - } else if ((d[0].type & (1u << SPA_DATA_DmaBuf)) > 0) { - assert(cast->buffer_type == DMABUF); - t = SPA_DATA_DmaBuf; - } else { - logprint(ERROR, "pipewire: unsupported buffer type"); - cast->err = 1; - return; - } - - logprint(TRACE, "pipewire: selected buffertype %u", t); - - struct xdpw_buffer *xdpw_buffer = xdpw_buffer_create(cast, cast->buffer_type, &cast->screencopy_frame_info[cast->buffer_type]); - if (xdpw_buffer == NULL) { - logprint(ERROR, "pipewire: failed to create xdpw buffer"); - cast->err = 1; - return; - } - wl_list_insert(&cast->buffer_list, &xdpw_buffer->link); - buffer->user_data = xdpw_buffer; - - assert(xdpw_buffer->plane_count >= 0 && buffer->buffer->n_datas == (uint32_t)xdpw_buffer->plane_count); - for (uint32_t plane = 0; plane < buffer->buffer->n_datas; plane++) { - d[plane].type = t; - d[plane].maxsize = xdpw_buffer->size[plane]; - d[plane].mapoffset = 0; - d[plane].chunk->size = xdpw_buffer->size[plane]; - d[plane].chunk->stride = xdpw_buffer->stride[plane]; - d[plane].chunk->offset = xdpw_buffer->offset[plane]; - d[plane].flags = 0; - d[plane].fd = xdpw_buffer->fd[plane]; - d[plane].data = NULL; - // clients have implemented to check chunk->size if the buffer is valid instead - // of using the flags. Until they are patched we should use some arbitrary value. - if (xdpw_buffer->buffer_type == DMABUF && d[plane].chunk->size == 0) { - d[plane].chunk->size = 9; // This was choosen by a fair d20. - } - } -} - -static void pwr_handle_stream_remove_buffer(void *data, struct pw_buffer *buffer) { - struct xdpw_screencast_instance *cast = data; - - logprint(DEBUG, "pipewire: remove buffer event handle"); - - struct xdpw_buffer *xdpw_buffer = buffer->user_data; - if (xdpw_buffer) { - xdpw_buffer_destroy(xdpw_buffer); - } - if (cast->current_frame.pw_buffer == buffer) { - cast->current_frame.pw_buffer = NULL; - } - for (uint32_t plane = 0; plane < buffer->buffer->n_datas; plane++) { - buffer->buffer->datas[plane].fd = -1; - } - buffer->user_data = NULL; -} - -static const struct pw_stream_events pwr_stream_events = { - PW_VERSION_STREAM_EVENTS, - .state_changed = pwr_handle_stream_state_changed, - .param_changed = pwr_handle_stream_param_changed, - .add_buffer = pwr_handle_stream_add_buffer, - .remove_buffer = pwr_handle_stream_remove_buffer, -}; - -void xdpw_pwr_dequeue_buffer(struct xdpw_screencast_instance *cast) { - logprint(TRACE, "pipewire: dequeueing buffer"); - - assert(!cast->current_frame.pw_buffer); - if ((cast->current_frame.pw_buffer = pw_stream_dequeue_buffer(cast->stream)) == NULL) { - logprint(WARN, "pipewire: out of buffers"); - return; - } - - cast->current_frame.xdpw_buffer = cast->current_frame.pw_buffer->user_data; -} - -void xdpw_pwr_enqueue_buffer(struct xdpw_screencast_instance *cast) { - logprint(TRACE, "pipewire: enqueueing buffer"); - - if (!cast->current_frame.pw_buffer) { - logprint(WARN, "pipewire: no buffer to queue"); - goto done; - } - struct pw_buffer *pw_buf = cast->current_frame.pw_buffer; - struct spa_buffer *spa_buf = pw_buf->buffer; - struct spa_data *d = spa_buf->datas; - - bool buffer_corrupt = cast->frame_state != XDPW_FRAME_STATE_SUCCESS; - - if (cast->current_frame.y_invert) { - //TODO: Flip buffer or set stride negative - buffer_corrupt = true; - cast->err = 1; - } - - logprint(TRACE, "********************"); - struct spa_meta_header *h; - if ((h = spa_buffer_find_meta_data(spa_buf, SPA_META_Header, sizeof(*h)))) { - h->pts = SPA_TIMESPEC_TO_NSEC(&cast->current_frame); - h->flags = buffer_corrupt ? SPA_META_HEADER_FLAG_CORRUPTED : 0; - h->seq = cast->seq++; - h->dts_offset = 0; - logprint(TRACE, "pipewire: timestamp %"PRId64, h->pts); - } - - struct spa_meta_videotransform *vt; - if ((vt = spa_buffer_find_meta_data(spa_buf, SPA_META_VideoTransform, sizeof(*vt)))) { - vt->transform = cast->target.output ? cast->target.output->transform : 0; - logprint(TRACE, "pipewire: transform %u", vt->transform); - } - - struct spa_meta *damage; - if ((damage = spa_buffer_find_meta(spa_buf, SPA_META_VideoDamage))) { - struct spa_region *d_region = spa_meta_first(damage); - uint32_t damage_counter = 0; - do { - if (damage_counter >= cast->current_frame.damage_count) { - *d_region = SPA_REGION(0, 0, 0, 0); - logprint(TRACE, "pipewire: end damage %u %u,%u (%ux%u)", damage_counter, - d_region->position.x, d_region->position.y, d_region->size.width, d_region->size.height); - break; - } - *d_region = SPA_REGION(cast->current_frame.damage[damage_counter].x, - cast->current_frame.damage[damage_counter].y, - cast->current_frame.damage[damage_counter].width, - cast->current_frame.damage[damage_counter].height); - logprint(TRACE, "pipewire: damage %u %u,%u (%ux%u)", damage_counter, - d_region->position.x, d_region->position.y, d_region->size.width, d_region->size.height); - damage_counter++; - } while (spa_meta_check(d_region + 1, damage) && d_region++); - - if (damage_counter < cast->current_frame.damage_count) { - struct xdpw_frame_damage damage = - {d_region->position.x, d_region->position.x, d_region->size.width, d_region->size.height}; - for (; damage_counter < cast->current_frame.damage_count; damage_counter++) { - damage = merge_damage(&damage, &cast->current_frame.damage[damage_counter]); - } - *d_region = SPA_REGION(damage.x, damage.y, damage.width, damage.height); - logprint(TRACE, "pipewire: collected damage %u %u,%u (%ux%u)", damage_counter, - d_region->position.x, d_region->position.y, d_region->size.width, d_region->size.height); - } - } - - if (buffer_corrupt) { - for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) { - d[plane].chunk->flags = SPA_CHUNK_FLAG_CORRUPTED; - } - } else { - for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) { - d[plane].chunk->flags = SPA_CHUNK_FLAG_NONE; - } - } - - for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) { - logprint(TRACE, "pipewire: plane %d", plane); - logprint(TRACE, "pipewire: fd %u", d[plane].fd); - logprint(TRACE, "pipewire: maxsize %d", d[plane].maxsize); - logprint(TRACE, "pipewire: size %d", d[plane].chunk->size); - logprint(TRACE, "pipewire: stride %d", d[plane].chunk->stride); - logprint(TRACE, "pipewire: offset %d", d[plane].chunk->offset); - logprint(TRACE, "pipewire: chunk flags %d", d[plane].chunk->flags); - } - logprint(TRACE, "pipewire: width %d", cast->current_frame.xdpw_buffer->width); - logprint(TRACE, "pipewire: height %d", cast->current_frame.xdpw_buffer->height); - logprint(TRACE, "pipewire: y_invert %d", cast->current_frame.y_invert); - logprint(TRACE, "********************"); - - pw_stream_queue_buffer(cast->stream, pw_buf); - -done: - cast->current_frame.xdpw_buffer = NULL; - cast->current_frame.pw_buffer = NULL; -} - -void pwr_update_stream_param(struct xdpw_screencast_instance *cast) { - logprint(TRACE, "pipewire: stream update parameters"); - struct pw_stream *stream = cast->stream; - uint8_t params_buffer[2][1024]; - struct spa_pod_dynamic_builder b[2]; - spa_pod_dynamic_builder_init(&b[0], params_buffer[0], sizeof(params_buffer[0]), 2048); - spa_pod_dynamic_builder_init(&b[1], params_buffer[1], sizeof(params_buffer[1]), 2048); - const struct spa_pod *params[2]; - - struct spa_pod_builder *builder[2] = {&b[0].b, &b[1].b}; - uint32_t n_params = build_formats(builder, cast, params); - - pw_stream_update_params(stream, params, n_params); - spa_pod_dynamic_builder_clean(&b[0]); - spa_pod_dynamic_builder_clean(&b[1]); -} - -void xdpw_pwr_stream_create(struct xdpw_screencast_instance *cast) { - struct xdpw_screencast_context *ctx = cast->ctx; - struct xdpw_state *state = ctx->state; - - pw_loop_enter(state->pw_loop); - - uint8_t buffer[2][1024]; - struct spa_pod_dynamic_builder b[2]; - spa_pod_dynamic_builder_init(&b[0], buffer[0], sizeof(buffer[0]), 2048); - spa_pod_dynamic_builder_init(&b[1], buffer[1], sizeof(buffer[1]), 2048); - const struct spa_pod *params[2]; - - char name[] = "xdpw-stream-XXXXXX"; - randname(name + strlen(name) - 6); - cast->stream = pw_stream_new(ctx->core, name, - pw_properties_new( - PW_KEY_MEDIA_CLASS, "Video/Source", - NULL)); - - if (!cast->stream) { - logprint(ERROR, "pipewire: failed to create stream"); - abort(); - } - cast->pwr_stream_state = false; - - struct spa_pod_builder *builder[2] = {&b[0].b, &b[1].b}; - uint32_t param_count = build_formats(builder, cast, params); - spa_pod_dynamic_builder_clean(&b[0]); - spa_pod_dynamic_builder_clean(&b[1]); - - pw_stream_add_listener(cast->stream, &cast->stream_listener, - &pwr_stream_events, cast); - - pw_stream_connect(cast->stream, - PW_DIRECTION_OUTPUT, - PW_ID_ANY, - (PW_STREAM_FLAG_DRIVER | - PW_STREAM_FLAG_ALLOC_BUFFERS), - params, param_count); -} - -void xdpw_pwr_stream_destroy(struct xdpw_screencast_instance *cast) { - if (!cast->stream) { - return; - } - - logprint(DEBUG, "pipewire: destroying stream"); - pw_stream_flush(cast->stream, false); - pw_stream_disconnect(cast->stream); - pw_stream_destroy(cast->stream); - cast->stream = NULL; -} - -int xdpw_pwr_context_create(struct xdpw_state *state) { - struct xdpw_screencast_context *ctx = &state->screencast; - - logprint(DEBUG, "pipewire: establishing connection to core"); - - if (!ctx->pwr_context) { - ctx->pwr_context = pw_context_new(state->pw_loop, NULL, 0); - if (!ctx->pwr_context) { - logprint(ERROR, "pipewire: failed to create context"); - return -1; - } - } - - if (!ctx->core) { - ctx->core = pw_context_connect(ctx->pwr_context, NULL, 0); - if (!ctx->core) { - logprint(ERROR, "pipewire: couldn't connect to context"); - return -1; - } - } - return 0; -} - -void xdpw_pwr_context_destroy(struct xdpw_state *state) { - struct xdpw_screencast_context *ctx = &state->screencast; - - logprint(DEBUG, "pipewire: disconnecting fom core"); - - if (ctx->core) { - pw_core_disconnect(ctx->core); - ctx->core = NULL; - } - - if (ctx->pwr_context) { - pw_context_destroy(ctx->pwr_context); - ctx->pwr_context = NULL; - } -} diff --git a/src/screencast/screencast.c b/src/screencast/screencast.c deleted file mode 100644 index 89115e2..0000000 --- a/src/screencast/screencast.c +++ /dev/null @@ -1,731 +0,0 @@ -#include "screencast.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "logger.h" -#include "pipewire_screencast.h" -#include "wlr_screencast.h" -#include "xdpw.h" - -static const char object_path[] = "/org/freedesktop/portal/desktop"; -static const char interface_name[] = "org.freedesktop.impl.portal.ScreenCast"; - -void exec_with_shell(char *command) { - pid_t pid1 = fork(); - if (pid1 < 0) { - perror("fork"); - return; - } else if (pid1 == 0) { - pid_t pid2 = fork(); - if (pid2 < 0) { - perror("fork"); - } else if (pid2 == 0) { - char *const argv[] = { - "sh", - "-c", - command, - NULL, - }; - execvp("sh", argv); - perror("execvp"); - _exit(127); - } - _exit(0); - } - int stat; - if (waitpid(pid1, &stat, 0) < 0) { - perror("waitpid"); - } -} - -void xdpw_screencast_instance_init(struct xdpw_screencast_context *ctx, struct xdpw_screencast_instance *cast, struct xdpw_share out, - bool with_cursor) { - // only run exec_before if there's no other instance running that already ran it - if (wl_list_empty(&ctx->screencast_instances)) { - char *exec_before = ctx->state->config->screencast_conf.exec_before; - if (exec_before) { - logprint(INFO, "xdpw: executing %s before screencast", exec_before); - exec_with_shell(exec_before); - } - } - - cast->ctx = ctx; - cast->target = out; - if (out.output == NULL) { - cast->max_framerate = 60; // dirty - } else { - if (ctx->state->config->screencast_conf.max_fps > 0) { - cast->max_framerate = ctx->state->config->screencast_conf.max_fps < (uint32_t)out.output->framerate - ? ctx->state->config->screencast_conf.max_fps - : (uint32_t)out.output->framerate; - } else { - cast->max_framerate = (uint32_t)out.output->framerate; - } - } - - cast->framerate = cast->max_framerate; - cast->with_cursor = with_cursor; - cast->refcount = 1; - cast->node_id = SPA_ID_INVALID; - cast->avoid_dmabufs = false; - cast->teardown = false; - wl_list_init(&cast->buffer_list); - logprint(INFO, "xdpw: screencast instance %p has %d references", cast, cast->refcount); - wl_list_insert(&ctx->screencast_instances, &cast->link); - logprint(INFO, "xdpw: %d active screencast instances", wl_list_length(&ctx->screencast_instances)); -} - -void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast) { - assert(cast->refcount == 0); // Fails assert if called by screencast_finish - logprint(DEBUG, "xdpw: destroying cast instance"); - - // make sure this is the last running instance that is being destroyed - if (wl_list_length(&cast->link) == 1) { - char *exec_after = cast->ctx->state->config->screencast_conf.exec_after; - if (exec_after) { - logprint(INFO, "xdpw: executing %s after screencast", exec_after); - exec_with_shell(exec_after); - } - } - - wl_list_remove(&cast->link); - xdpw_pwr_stream_destroy(cast); - assert(wl_list_length(&cast->buffer_list) == 0); - free(cast); -} - -void xdpw_screencast_instance_teardown(struct xdpw_screencast_instance *cast) { - struct xdpw_session *sess, *tmp; - wl_list_for_each_safe(sess, tmp, &cast->ctx->state->xdpw_sessions, link) { - if (sess->screencast_instance == cast) { - xdpw_session_destroy(sess); - } - } -} - -bool setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *sess, bool with_cursor, struct xdph_restore_token *token) { - struct xdpw_wlr_output *output, *tmp_o; - wl_list_for_each_reverse_safe(output, tmp_o, &ctx->output_list, link) { - logprint(INFO, "wlroots: capturable output: %s model: %s: id: %i name: %s", output->make, output->model, output->id, output->name); - } - - struct xdpw_share out; - out.window_handle = -1; - out.x = -1; - out.y = -1; - out.w = -1; - out.h = -1; - out.output = NULL; - bool tokenSuccess = false; - - if (token) { - // attempt to restore - if (token->outputPort) { - if (strncmp(token->outputPort, "class:", 6) == 0) { - struct SToplevelEntry *current; - wl_list_for_each(current, &ctx->toplevel_resource_list, link) { - if (strcmp(token->outputPort + 6, current->clazz) == 0) { - out.window_handle = token->windowHandle; - tokenSuccess = true; - break; - } - } - } else { - struct xdpw_wlr_output *output; - wl_list_for_each(output, &ctx->output_list, link) { - if (strcmp(output->name, token->outputPort) == 0) { - out.output = output; - tokenSuccess = true; - break; - } - } - } - - } else if (token->windowHandle > 0) { - struct SToplevelEntry *current; - wl_list_for_each(current, &ctx->toplevel_resource_list, link) { - if (((uint64_t)current->handle & 0xFFFFFFFF) == token->windowHandle) { - out.window_handle = token->windowHandle; - tokenSuccess = true; - break; - } - } - } - } - - if (!tokenSuccess) out = xdpw_wlr_chooser(ctx); - - if (!out.output && out.window_handle == -1) { - logprint(ERROR, "wlroots: no output / window found"); - return false; - } - - // Disable screencast sharing to avoid sharing between dmabuf and shm capable clients - /* - struct xdpw_screencast_instance *cast, *tmp_c; - wl_list_for_each_reverse_safe(cast, tmp_c, &ctx->screencast_instances, link) { - logprint(INFO, "xdpw: existing screencast instance: %d %s cursor", - cast->target_output->id, - cast->with_cursor ? "with" : "without"); - - if (cast->target_output->id == out->id && cast->with_cursor == with_cursor) { - if (cast->refcount == 0) { - logprint(DEBUG, - "xdpw: matching cast instance found, " - "but is already scheduled for destruction, skipping"); - } - else { - sess->screencast_instance = cast; - ++cast->refcount; - } - logprint(INFO, "xdpw: screencast instance %p now has %d references", - cast, cast->refcount); - } - } - */ - - if (!sess->screencast_instance) { - sess->screencast_instance = calloc(1, sizeof(struct xdpw_screencast_instance)); - xdpw_screencast_instance_init(ctx, sess->screencast_instance, out, with_cursor); - } - if (out.output) { - logprint(INFO, "wlroots: output: %s", sess->screencast_instance->target.output->name); - } else { - logprint(INFO, "hyprland: window handle %d", sess->screencast_instance->target.window_handle); - } - - return true; -} - -static int start_screencast(struct xdpw_screencast_instance *cast) { - xdpw_wlr_register_cb(cast); - - // process at least one frame so that we know - // some of the metadata required for the pipewire - // remote state connected event - wl_display_dispatch(cast->ctx->state->wl_display); - wl_display_roundtrip(cast->ctx->state->wl_display); - - if (cast->screencopy_frame_info[WL_SHM].format == DRM_FORMAT_INVALID /*|| - (cast->ctx->state->screencast_version >= 3 && cast->screencopy_frame_info[DMABUF].format == DRM_FORMAT_INVALID)*/) { - logprint(INFO, "wlroots: unable to receive a valid format from wlr_screencopy"); - return -1; - } - - xdpw_pwr_stream_create(cast); - - cast->initialized = true; - return 0; -} - -static struct xdph_restore_token *findRestoreToken(char *token, struct xdpw_state *state) { - struct xdph_restore_token *current; - wl_list_for_each(current, &state->restore_tokens, link) { - if (strcmp(current->token, token) == 0) { - return current; - } - } - - return NULL; -} - -static struct xdph_restore_token *getRestoreToken(char *sessionToken, struct xdpw_state *state, char *outputSelected, uint64_t windowSelected, - bool withCursor) { - state->lastRestoreToken++; - uuid_t uuid; - uuid_generate_random(uuid); - char *uuid_str = malloc(37); - uuid_unparse_upper(uuid, uuid_str); - while (findRestoreToken(uuid_str, state) != NULL) { - uuid_generate_random(uuid); - uuid_unparse_upper(uuid, uuid_str); - } - - struct xdph_restore_token *restoreToken = calloc(1, sizeof(struct xdph_restore_token)); - if (outputSelected) { - restoreToken->outputPort = strdup(outputSelected); - } - restoreToken->windowHandle = windowSelected; - - restoreToken->token = uuid_str; - restoreToken->withCursor = withCursor; - - if (windowSelected) { - struct SToplevelEntry *current; - wl_list_for_each(current, &state->screencast.toplevel_resource_list, link) { - if (current->handle == restoreToken->windowHandle) { - restoreToken->windowClass = getFormat("class:%x", current->clazz); - break; - } - } - } - - return restoreToken; -} - -static int method_screencast_create_session(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { - struct xdpw_state *state = data; - - int ret = 0; - - logprint(INFO, "dbus: create session method invoked"); - - char *request_handle, *session_handle, *app_id; - ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id); - if (ret < 0) { - return ret; - } - - ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - - logprint(INFO, "dbus: request_handle: %s", request_handle); - logprint(INFO, "dbus: session_handle: %s", session_handle); - logprint(INFO, "dbus: app_id: %s", app_id); - - char *key; - int innerRet = 0; - while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { - innerRet = sd_bus_message_read(msg, "s", &key); - if (innerRet < 0) { - return innerRet; - } - - if (strcmp(key, "session_handle_token") == 0) { - char *token; - sd_bus_message_read(msg, "v", "s", &token); - logprint(INFO, "dbus: option token: %s", token); - } else { - logprint(WARN, "dbus: unknown option: %s", key); - sd_bus_message_skip(msg, "v"); - } - - innerRet = sd_bus_message_exit_container(msg); - if (innerRet < 0) { - return innerRet; - } - } - if (ret < 0) { - return ret; - } - - ret = sd_bus_message_exit_container(msg); - if (ret < 0) { - return ret; - } - - struct xdpw_request *req = xdpw_request_create(sd_bus_message_get_bus(msg), request_handle); - if (req == NULL) { - return -ENOMEM; - } - - struct xdpw_session *sess = xdpw_session_create(state, sd_bus_message_get_bus(msg), strdup(session_handle)); - if (sess == NULL) { - return -ENOMEM; - } - - sd_bus_message *reply = NULL; - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 0); - if (ret < 0) { - return ret; - } - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - - sd_bus_message_unref(reply); - return 0; -} - -static int method_screencast_select_sources(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { - struct xdpw_state *state = data; - struct xdpw_screencast_context *ctx = &state->screencast; - - int ret = 0; - struct xdpw_session *sess, *tmp_s; - sd_bus_message *reply = NULL; - - logprint(INFO, "dbus: select sources method invoked"); - - // default to embedded cursor mode if not specified - bool cursor_embedded = true; - - char *request_handle, *session_handle, *app_id; - ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - - logprint(INFO, "dbus: request_handle: %s", request_handle); - logprint(INFO, "dbus: session_handle: %s", session_handle); - logprint(INFO, "dbus: app_id: %s", app_id); - - char *key; - int innerRet = 0; - uint32_t persist = 0; - - struct xdph_restore_token *foundToken = NULL; - - while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { - innerRet = sd_bus_message_read(msg, "s", &key); - if (innerRet < 0) { - return innerRet; - } - - if (strcmp(key, "multiple") == 0) { - int multiple; - sd_bus_message_read(msg, "v", "b", &multiple); - logprint(INFO, "dbus: option multiple: %d", multiple); - } else if (strcmp(key, "types") == 0) { - uint32_t mask; - sd_bus_message_read(msg, "v", "u", &mask); - if (mask & (1 << WINDOW)) { - logprint(INFO, "dbus: non-monitor cast requested, not replying"); - return -1; - } - logprint(INFO, "dbus: option types:%x", mask); - } else if (strcmp(key, "cursor_mode") == 0) { - uint32_t cursor_mode; - sd_bus_message_read(msg, "v", "u", &cursor_mode); - if (cursor_mode & HIDDEN) { - cursor_embedded = false; - } - if (cursor_mode & METADATA) { - logprint(ERROR, "dbus: unsupported cursor mode requested"); - goto error; - } - logprint(INFO, "dbus: option cursor_mode:%x", cursor_mode); - } else if (strcmp(key, "restore_token") == 0) { - char *restoreToken; - sd_bus_message_read(msg, "v", "s", &restoreToken); - - logprint(INFO, "dbus: restore_token %s", restoreToken); - - foundToken = findRestoreToken(restoreToken, state); - } else if (strcmp(key, "restore_data") == 0) { - logprint(INFO, "dbus: restore_data"); - innerRet = sd_bus_message_enter_container(msg, 'v', "(suv)"); - if (innerRet < 0) { - logprint(ERROR, "dbus: restore_data malformed container"); - return innerRet; - } - innerRet = sd_bus_message_enter_container(msg, 'r', "suv"); - if (innerRet < 0) { - logprint(ERROR, "dbus: error entering struct"); - return innerRet; - } - char *issuer; - sd_bus_message_read(msg, "s", &issuer); - if (strcmp(issuer, "hyprland") != 0) { - logprint(INFO, "dbus: skipping unknown issuer (%s)", issuer); - sd_bus_message_skip(msg, "uv"); - continue; - } - uint32_t ver; - sd_bus_message_read(msg, "u", &ver); - - if (ver == 1) { - char *restoreToken; - sd_bus_message_read(msg, "v", "s", &restoreToken); - - logprint(INFO, "dbus: restore_token %s", restoreToken); - - foundToken = findRestoreToken(restoreToken, state); - - if (foundToken) - logprint(INFO, "xdph: found token %s", restoreToken); - else - logprint(INFO, "xdph: token not found"); - } else if (ver == 2) { - innerRet = sd_bus_message_enter_container(msg, 'v', "(susbt)" /* amogus */); - innerRet = sd_bus_message_enter_container(msg, 'r', "susbt" /* amogus */); - if (innerRet < 0) { - logprint(ERROR, "dbus: error entering struct"); - return innerRet; - } - - char *token; - char *output; - uint32_t windowHandle; - bool withCursor; - uint64_t timeIssued; - - sd_bus_message_read(msg, "s", &token); - sd_bus_message_read(msg, "u", &windowHandle); - sd_bus_message_read(msg, "s", &output); - sd_bus_message_read(msg, "b", &withCursor); - sd_bus_message_read(msg, "t", &timeIssued); - - foundToken = findRestoreToken(token, state); - - if (!foundToken) { - foundToken = getRestoreToken(session_handle, state, strlen(output) == 0 ? NULL : output, windowHandle, withCursor); - } - - sd_bus_message_exit_container(msg); - sd_bus_message_exit_container(msg); - - } else { - logprint(INFO, "dbus: skipping unknown ver (%d)", ver); - sd_bus_message_skip(msg, "v"); - } - - sd_bus_message_exit_container(msg); - sd_bus_message_exit_container(msg); - } else if (strcmp(key, "persist_mode") == 0) { - sd_bus_message_read(msg, "v", "u", &persist); - logprint(INFO, "dbus: persist %d", persist); - } else { - logprint(WARN, "dbus: unknown option %s", key); - sd_bus_message_skip(msg, "v"); - } - - innerRet = sd_bus_message_exit_container(msg); - if (ret < 0) { - return ret; - } - } - if (ret < 0) { - return ret; - } - ret = sd_bus_message_exit_container(msg); - if (ret < 0) { - return ret; - } - - bool output_selection_canceled = 1; - wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) { - if (strcmp(sess->session_handle, session_handle) == 0) { - logprint(DEBUG, "dbus: select sources: found matching session %s", sess->session_handle); - - output_selection_canceled = !setup_outputs(ctx, sess, cursor_embedded, foundToken); - - sess->persist = persist; - - // foundToken has been used, if it existed - if (foundToken) { - wl_list_remove(&foundToken->link); - free(foundToken); - foundToken = NULL; - } - } - } - - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - if (output_selection_canceled) { - ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_CANCELLED, 0); - } else { - ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 0); - } - if (ret < 0) { - return ret; - } - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - sd_bus_message_unref(reply); - return 0; - -error: - wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) { - if (strcmp(sess->session_handle, session_handle) == 0) { - logprint(DEBUG, "dbus: select sources error: destroying matching session %s", sess->session_handle); - xdpw_session_destroy(sess); - } - } - - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_CANCELLED, 0); - if (ret < 0) { - return ret; - } - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - sd_bus_message_unref(reply); - return -1; -} - -static int method_screencast_start(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { - struct xdpw_state *state = data; - - int ret = 0; - - logprint(INFO, "dbus: start method invoked"); - - char *request_handle, *session_handle, *app_id, *parent_window; - ret = sd_bus_message_read(msg, "ooss", &request_handle, &session_handle, &app_id, &parent_window); - if (ret < 0) { - return ret; - } - ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - - logprint(INFO, "dbus: request_handle: %s", request_handle); - logprint(INFO, "dbus: session_handle: %s", session_handle); - logprint(INFO, "dbus: app_id: %s", app_id); - logprint(INFO, "dbus: parent_window: %s", parent_window); - - char *key; - int innerRet = 0; - while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { - innerRet = sd_bus_message_read(msg, "s", &key); - if (innerRet < 0) { - return innerRet; - } - logprint(WARN, "dbus: unknown option: %s", key); - sd_bus_message_skip(msg, "v"); - innerRet = sd_bus_message_exit_container(msg); - if (innerRet < 0) { - return innerRet; - } - } - if (ret < 0) { - return ret; - } - ret = sd_bus_message_exit_container(msg); - if (ret < 0) { - return ret; - } - - struct xdpw_screencast_instance *cast = NULL; - struct xdpw_session *sess, *tmp_s; - wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) { - if (strcmp(sess->session_handle, session_handle) == 0) { - logprint(DEBUG, "dbus: start: found matching session %s", sess->session_handle); - cast = sess->screencast_instance; - } - } - if (!cast) { - return -1; - } - - if (!cast->initialized) { - ret = start_screencast(cast); - } - if (ret < 0) { - return ret; - } - - while (cast->node_id == SPA_ID_INVALID) { - int ret = pw_loop_iterate(state->pw_loop, 0); - if (ret < 0) { - logprint(ERROR, "pipewire_loop_iterate failed: %s", spa_strerror(ret)); - return ret; - } - } - - // create token - struct xdph_restore_token *restoreToken = NULL; - if (sess->persist) { - restoreToken = getRestoreToken(session_handle, state, cast->target.output ? cast->target.output->name : NULL, - cast->target.window_handle < 1 ? 0 : cast->target.window_handle, cast->with_cursor); - - wl_list_insert(&state->restore_tokens, &restoreToken->link); - - logprint(INFO, "xdph: registered restoreToken with token %s", restoreToken->token); - } - - sd_bus_message *reply = NULL; - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - - logprint(DEBUG, "dbus: start: returning node %d", (int)cast->node_id); - if (restoreToken) - ret = sd_bus_message_append( - reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 3, "streams", "a(ua{sv})", 1, cast->node_id, 3, "position", "(ii)", 0, 0, "size", "(ii)", - cast->screencopy_frame_info[WL_SHM].width, cast->screencopy_frame_info[WL_SHM].height, "source_type", "u", - (cast->target.output ? (1 << MONITOR) : (1 << WINDOW)), "persist_mode", "u", sess->persist, "restore_data", "(suv)", "hyprland", 2, - "(susbt)" /* amogus */, restoreToken->token, restoreToken->windowHandle, - (restoreToken->outputPort == NULL ? (restoreToken->windowHandle ? restoreToken->windowClass : "") : restoreToken->outputPort), - restoreToken->withCursor, (unsigned long)time(NULL)); - else - ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1, "streams", "a(ua{sv})", 1, cast->node_id, 3, "position", "(ii)", 0, - 0, "size", "(ii)", cast->screencopy_frame_info[WL_SHM].width, cast->screencopy_frame_info[WL_SHM].height, - "source_type", "u", (cast->target.output ? (1 << MONITOR) : (1 << WINDOW))); - - if (ret < 0) { - return ret; - } - - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - sd_bus_message_unref(reply); - - return 0; -} - -static const sd_bus_vtable screencast_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("CreateSession", "oosa{sv}", "ua{sv}", method_screencast_create_session, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SelectSources", "oosa{sv}", "ua{sv}", method_screencast_select_sources, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Start", "oossa{sv}", "ua{sv}", method_screencast_start, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_PROPERTY("AvailableSourceTypes", "u", NULL, offsetof(struct xdpw_state, screencast_source_types), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("AvailableCursorModes", "u", NULL, offsetof(struct xdpw_state, screencast_cursor_modes), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("version", "u", NULL, offsetof(struct xdpw_state, screencast_version), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END}; - -int xdpw_screencast_init(struct xdpw_state *state) { - sd_bus_slot *slot = NULL; - - state->screencast = (struct xdpw_screencast_context){0}; - state->screencast.state = state; - state->screencast.hyprland_toplevel_manager = NULL; - - int err; - err = xdpw_pwr_context_create(state); - if (err) { - goto fail_pipewire; - } - - err = xdpw_wlr_screencopy_init(state); - if (err) { - goto fail_screencopy; - } - - return sd_bus_add_object_vtable(state->bus, &slot, object_path, interface_name, screencast_vtable, state); - -fail_screencopy: - xdpw_wlr_screencopy_finish(&state->screencast); - -fail_pipewire: - xdpw_pwr_context_destroy(state); - - return err; -} diff --git a/src/screencast/screencast_common.c b/src/screencast/screencast_common.c deleted file mode 100644 index 696a225..0000000 --- a/src/screencast/screencast_common.c +++ /dev/null @@ -1,426 +0,0 @@ -#include "xdpw.h" -#include "screencast_common.h" -#include -#include -#include -#include -#include -#include -#include -#include "linux-dmabuf-unstable-v1-client-protocol.h" - -#include "logger.h" - -void randname(char *buf) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - long r = ts.tv_nsec; - for (int i = 0; i < 6; ++i) { - assert(buf[i] == 'X'); - buf[i] = 'A'+(r&15)+(r&16)*2; - r >>= 5; - } -} - -static char *gbm_find_render_node(drmDevice *device) { - drmDevice *devices[64]; - char *render_node = NULL; - - int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0])); - for (int i = 0; i < n; ++i) { - drmDevice *dev = devices[i]; - if (device && !drmDevicesEqual(device, dev)) { - continue; - } - if (!(dev->available_nodes & (1 << DRM_NODE_RENDER))) - continue; - - render_node = strdup(dev->nodes[DRM_NODE_RENDER]); - break; - } - - drmFreeDevices(devices, n); - return render_node; -} - -struct gbm_device *xdpw_gbm_device_create(drmDevice *device) { - struct gbm_device *gbm; - char *render_node = NULL; - - render_node = gbm_find_render_node(device); - if (render_node == NULL) { - logprint(ERROR, "xdpw: Could not find render node"); - return NULL; - } - logprint(INFO, "xdpw: Using render node %s", render_node); - - int fd = open(render_node, O_RDWR | O_CLOEXEC); - if (fd < 0) { - logprint(ERROR, "xdpw: Could not open render node %s", render_node); - free(render_node); - return NULL; - } - - free(render_node); - gbm = gbm_create_device(fd); - return gbm; -} - -static int anonymous_shm_open(void) { - char name[] = "/xdpw-shm-XXXXXX"; - int retries = 100; - - do { - randname(name + strlen(name) - 6); - - --retries; - // shm_open guarantees that O_CLOEXEC is set - int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); - if (fd >= 0) { - shm_unlink(name); - return fd; - } - } while (retries > 0 && errno == EEXIST); - - return -1; -} - -static struct wl_buffer *import_wl_shm_buffer(struct xdpw_screencast_instance *cast, int fd, - enum wl_shm_format fmt, int width, int height, int stride) { - struct xdpw_screencast_context *ctx = cast->ctx; - int size = stride * height; - - if (fd < 0) { - return NULL; - } - - struct wl_shm_pool *pool = wl_shm_create_pool(ctx->shm, fd, size); - struct wl_buffer *buffer = - wl_shm_pool_create_buffer(pool, 0, width, height, stride, fmt); - wl_shm_pool_destroy(pool); - - return buffer; -} - -struct xdpw_buffer *xdpw_buffer_create(struct xdpw_screencast_instance *cast, - enum buffer_type buffer_type, struct xdpw_screencopy_frame_info *frame_info) { - struct xdpw_buffer *buffer = calloc(1, sizeof(struct xdpw_buffer)); - buffer->width = frame_info->width; - buffer->height = frame_info->height; - buffer->format = frame_info->format; - buffer->buffer_type = buffer_type; - - switch (buffer_type) { - case WL_SHM: - buffer->plane_count = 1; - buffer->size[0] = frame_info->size; - buffer->stride[0] = frame_info->stride; - buffer->offset[0] = 0; - buffer->fd[0] = anonymous_shm_open(); - if (buffer->fd[0] == -1) { - logprint(ERROR, "xdpw: unable to create anonymous filedescriptor"); - free(buffer); - return NULL; - } - - if (ftruncate(buffer->fd[0], buffer->size[0]) < 0) { - logprint(ERROR, "xdpw: unable to truncate filedescriptor"); - close(buffer->fd[0]); - free(buffer); - return NULL; - } - - buffer->buffer = import_wl_shm_buffer(cast, buffer->fd[0], xdpw_format_wl_shm_from_drm_fourcc(frame_info->format), - frame_info->width, frame_info->height, frame_info->stride); - if (buffer->buffer == NULL) { - logprint(ERROR, "xdpw: unable to create wl_buffer"); - close(buffer->fd[0]); - free(buffer); - return NULL; - } - break; - case DMABUF:; - uint32_t flags = GBM_BO_USE_RENDERING; - if (cast->pwr_format.modifier != DRM_FORMAT_MOD_INVALID) { - uint64_t *modifiers = (uint64_t*)&cast->pwr_format.modifier; - buffer->bo = gbm_bo_create_with_modifiers2(cast->ctx->gbm, frame_info->width, frame_info->height, - frame_info->format, modifiers, 1, flags); - } else { - if (cast->ctx->state->config->screencast_conf.force_mod_linear) { - flags |= GBM_BO_USE_LINEAR; - } - buffer->bo = gbm_bo_create(cast->ctx->gbm, frame_info->width, frame_info->height, - frame_info->format, flags); - } - - // Fallback for linear buffers via the implicit api - if (buffer->bo == NULL && cast->pwr_format.modifier == DRM_FORMAT_MOD_LINEAR) { - buffer->bo = gbm_bo_create(cast->ctx->gbm, frame_info->width, frame_info->height, - frame_info->format, flags | GBM_BO_USE_LINEAR); - } - - if (buffer->bo == NULL) { - logprint(ERROR, "xdpw: failed to create gbm_bo"); - free(buffer); - return NULL; - } - buffer->plane_count = gbm_bo_get_plane_count(buffer->bo); - - struct zwp_linux_buffer_params_v1 *params; - params = zwp_linux_dmabuf_v1_create_params(cast->ctx->linux_dmabuf); - if (!params) { - logprint(ERROR, "xdpw: failed to create linux_buffer_params"); - gbm_bo_destroy(buffer->bo); - free(buffer); - return NULL; - } - - for (int plane = 0; plane < buffer->plane_count; plane++) { - buffer->size[plane] = 0; - buffer->stride[plane] = gbm_bo_get_stride_for_plane(buffer->bo, plane); - buffer->offset[plane] = gbm_bo_get_offset(buffer->bo, plane); - uint64_t mod = gbm_bo_get_modifier(buffer->bo); - buffer->fd[plane] = gbm_bo_get_fd_for_plane(buffer->bo, plane); - - if (buffer->fd[plane] < 0) { - logprint(ERROR, "xdpw: failed to get file descriptor"); - zwp_linux_buffer_params_v1_destroy(params); - gbm_bo_destroy(buffer->bo); - for (int plane_tmp = 0; plane_tmp < plane; plane_tmp++) { - close(buffer->fd[plane_tmp]); - } - free(buffer); - return NULL; - } - - zwp_linux_buffer_params_v1_add(params, buffer->fd[plane], plane, - buffer->offset[plane], buffer->stride[plane], mod >> 32, mod & 0xffffffff); - } - buffer->buffer = zwp_linux_buffer_params_v1_create_immed(params, - buffer->width, buffer->height, - buffer->format, /* flags */ 0); - zwp_linux_buffer_params_v1_destroy(params); - - if (!buffer->buffer) { - logprint(ERROR, "xdpw: failed to create buffer"); - gbm_bo_destroy(buffer->bo); - for (int plane = 0; plane < buffer->plane_count; plane++) { - close(buffer->fd[plane]); - } - free(buffer); - return NULL; - } - } - - return buffer; -} - -void xdpw_buffer_destroy(struct xdpw_buffer *buffer) { - wl_buffer_destroy(buffer->buffer); - if (buffer->buffer_type == DMABUF) { - gbm_bo_destroy(buffer->bo); - } - for (int plane = 0; plane < buffer->plane_count; plane++) { - close(buffer->fd[plane]); - } - wl_list_remove(&buffer->link); - free(buffer); -} - -bool wlr_query_dmabuf_modifiers(struct xdpw_screencast_context *ctx, uint32_t drm_format, - uint32_t num_modifiers, uint64_t *modifiers, uint32_t *max_modifiers) { - if (ctx->format_modifier_pairs.size == 0) - return false; - struct xdpw_format_modifier_pair *fm_pair; - if (num_modifiers == 0) { - *max_modifiers = 0; - wl_array_for_each(fm_pair, &ctx->format_modifier_pairs) { - if (fm_pair->fourcc == drm_format && - (fm_pair->modifier == DRM_FORMAT_MOD_INVALID || - gbm_device_get_format_modifier_plane_count(ctx->gbm, fm_pair->fourcc, fm_pair->modifier) > 0)) - *max_modifiers += 1; - } - return true; - } - - uint32_t i = 0; - wl_array_for_each(fm_pair, &ctx->format_modifier_pairs) { - if (i == num_modifiers) - break; - if (fm_pair->fourcc == drm_format && - (fm_pair->modifier == DRM_FORMAT_MOD_INVALID || - gbm_device_get_format_modifier_plane_count(ctx->gbm, fm_pair->fourcc, fm_pair->modifier) > 0)) { - modifiers[i] = fm_pair->modifier; - i++; - } - } - *max_modifiers = num_modifiers; - return true; -} - -enum wl_shm_format xdpw_format_wl_shm_from_drm_fourcc(uint32_t format) { - switch (format) { - case DRM_FORMAT_ARGB8888: - return WL_SHM_FORMAT_ARGB8888; - case DRM_FORMAT_XRGB8888: - return WL_SHM_FORMAT_XRGB8888; - case DRM_FORMAT_RGBA8888: - case DRM_FORMAT_RGBX8888: - case DRM_FORMAT_ABGR8888: - case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_BGRA8888: - case DRM_FORMAT_BGRX8888: - case DRM_FORMAT_NV12: - case DRM_FORMAT_XRGB2101010: - case DRM_FORMAT_XBGR2101010: - case DRM_FORMAT_RGBX1010102: - case DRM_FORMAT_BGRX1010102: - case DRM_FORMAT_ARGB2101010: - case DRM_FORMAT_ABGR2101010: - case DRM_FORMAT_RGBA1010102: - case DRM_FORMAT_BGRA1010102: - return (enum wl_shm_format)format; - default: - logprint(ERROR, "xdg-desktop-portal-hyprland: unsupported drm " - "format 0x%08x", format); - abort(); - } -} - -uint32_t xdpw_format_drm_fourcc_from_wl_shm(enum wl_shm_format format) { - switch (format) { - case WL_SHM_FORMAT_ARGB8888: - return DRM_FORMAT_ARGB8888; - case WL_SHM_FORMAT_XRGB8888: - return DRM_FORMAT_XRGB8888; - case WL_SHM_FORMAT_RGBA8888: - case WL_SHM_FORMAT_RGBX8888: - case WL_SHM_FORMAT_ABGR8888: - case WL_SHM_FORMAT_XBGR8888: - case WL_SHM_FORMAT_BGRA8888: - case WL_SHM_FORMAT_BGRX8888: - case WL_SHM_FORMAT_NV12: - case WL_SHM_FORMAT_XRGB2101010: - case WL_SHM_FORMAT_XBGR2101010: - case WL_SHM_FORMAT_RGBX1010102: - case WL_SHM_FORMAT_BGRX1010102: - case WL_SHM_FORMAT_ARGB2101010: - case WL_SHM_FORMAT_ABGR2101010: - case WL_SHM_FORMAT_RGBA1010102: - case WL_SHM_FORMAT_BGRA1010102: - return (uint32_t)format; - default: - logprint(ERROR, "xdg-desktop-portal-hyprland: unsupported wl_shm " - "format 0x%08x", format); - abort(); - } -} - -enum spa_video_format xdpw_format_pw_from_drm_fourcc(uint32_t format) { - switch (format) { - case DRM_FORMAT_ARGB8888: - return SPA_VIDEO_FORMAT_BGRA; - case DRM_FORMAT_XRGB8888: - return SPA_VIDEO_FORMAT_BGRx; - case DRM_FORMAT_RGBA8888: - return SPA_VIDEO_FORMAT_ABGR; - case DRM_FORMAT_RGBX8888: - return SPA_VIDEO_FORMAT_xBGR; - case DRM_FORMAT_ABGR8888: - return SPA_VIDEO_FORMAT_RGBA; - case DRM_FORMAT_XBGR8888: - return SPA_VIDEO_FORMAT_RGBx; - case DRM_FORMAT_BGRA8888: - return SPA_VIDEO_FORMAT_ARGB; - case DRM_FORMAT_BGRX8888: - return SPA_VIDEO_FORMAT_xRGB; - case DRM_FORMAT_NV12: - return SPA_VIDEO_FORMAT_NV12; - case DRM_FORMAT_XRGB2101010: - return SPA_VIDEO_FORMAT_xRGB_210LE; - case DRM_FORMAT_XBGR2101010: - return SPA_VIDEO_FORMAT_xBGR_210LE; - case DRM_FORMAT_RGBX1010102: - return SPA_VIDEO_FORMAT_RGBx_102LE; - case DRM_FORMAT_BGRX1010102: - return SPA_VIDEO_FORMAT_BGRx_102LE; - case DRM_FORMAT_ARGB2101010: - return SPA_VIDEO_FORMAT_ARGB_210LE; - 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; - default: - logprint(ERROR, "xdg-desktop-portal-hyprland: failed to convert drm " - "format 0x%08x to spa_video_format", format); - abort(); - } -} - -enum spa_video_format xdpw_format_pw_strip_alpha(enum spa_video_format format) { - switch (format) { - case SPA_VIDEO_FORMAT_BGRA: - return SPA_VIDEO_FORMAT_BGRx; - case SPA_VIDEO_FORMAT_ABGR: - return SPA_VIDEO_FORMAT_xBGR; - case SPA_VIDEO_FORMAT_RGBA: - return SPA_VIDEO_FORMAT_RGBx; - case SPA_VIDEO_FORMAT_ARGB: - return SPA_VIDEO_FORMAT_xRGB; - case SPA_VIDEO_FORMAT_ARGB_210LE: - return SPA_VIDEO_FORMAT_xRGB_210LE; - case SPA_VIDEO_FORMAT_ABGR_210LE: - return SPA_VIDEO_FORMAT_xBGR_210LE; - case SPA_VIDEO_FORMAT_RGBA_102LE: - return SPA_VIDEO_FORMAT_RGBx_102LE; - case SPA_VIDEO_FORMAT_BGRA_102LE: - return SPA_VIDEO_FORMAT_BGRx_102LE; - default: - return SPA_VIDEO_FORMAT_UNKNOWN; - } -} - -enum xdpw_chooser_types get_chooser_type(const char *chooser_type) { - if (!chooser_type || strcmp(chooser_type, "default") == 0) { - return XDPW_CHOOSER_DEFAULT; - } else if (strcmp(chooser_type, "none") == 0) { - return XDPW_CHOOSER_NONE; - } else if (strcmp(chooser_type, "simple") == 0) { - return XDPW_CHOOSER_SIMPLE; - } else if (strcmp(chooser_type, "dmenu") == 0) { - return XDPW_CHOOSER_DMENU; - } - fprintf(stderr, "Could not understand chooser type %s\n", chooser_type); - exit(1); -} - -const char *chooser_type_str(enum xdpw_chooser_types chooser_type) { - switch (chooser_type) { - case XDPW_CHOOSER_DEFAULT: - return "default"; - case XDPW_CHOOSER_NONE: - return "none"; - case XDPW_CHOOSER_SIMPLE: - return "simple"; - case XDPW_CHOOSER_DMENU: - return "dmenu"; - } - fprintf(stderr, "Could not find chooser type %d\n", chooser_type); - abort(); -} - -struct xdpw_frame_damage merge_damage(struct xdpw_frame_damage *damage1, struct xdpw_frame_damage *damage2) { - struct xdpw_frame_damage damage; - uint32_t x0, y0; - damage.x = damage1->x < damage2->y ? damage1->x : damage2->x; - damage.y = damage1->y < damage2->y ? damage1->y : damage2->y; - - x0 = damage1->x + damage1->width < damage2->x + damage2->width ? damage2->x + damage2->width : damage1->x + damage1->width; - y0 = damage1->y + damage1->height < damage2->y + damage2->height ? damage2->y + damage2->height : damage1->y + damage1->height; - damage.width = x0 - damage.x; - damage.height = y0 - damage.y; - - return damage; -} diff --git a/src/screencast/wlr_screencast.c b/src/screencast/wlr_screencast.c deleted file mode 100644 index 2b439ac..0000000 --- a/src/screencast/wlr_screencast.c +++ /dev/null @@ -1,1154 +0,0 @@ -#include "wlr_screencast.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "fps_limit.h" -#include "hyprland-toplevel-export-v1-client-protocol.h" -#include "linux-dmabuf-unstable-v1-client-protocol.h" -#include "logger.h" -#include "pipewire_screencast.h" -#include "screencast.h" -#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" -#include "wlr-screencopy-unstable-v1-client-protocol.h" -#include "xdg-output-unstable-v1-client-protocol.h" -#include "xdpw.h" -// - -void handleTitle(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *title) { - struct xdpw_screencast_context *ctx = data; - - struct SToplevelEntry *current; - wl_list_for_each(current, &ctx->toplevel_resource_list, link) { - if (current->handle == handle) { - strncpy(current->name, title, 255); - for (int i = 0; i < 255; ++i) - if (current->name[i] == '\"' || current->name[i] == '>' || current->name[i] == '\'') current->name[i] = ' '; - current->name[255] = '\0'; - break; - } - } -} - -void handleAppID(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id) { - struct xdpw_screencast_context *ctx = data; - - struct SToplevelEntry *current; - wl_list_for_each(current, &ctx->toplevel_resource_list, link) { - if (current->handle == handle) { - strncpy(current->clazz, app_id, 255); - for (int i = 0; i < 255; ++i) - if (current->clazz[i] == '\"' || current->clazz[i] == '>' || current->clazz[i] == '\'') current->clazz[i] = ' '; - current->name[255] = '\0'; - break; - } - } - - logprint(DEBUG, "hyprland: toplevel app_id %s", app_id); -} - -void handleOutputEnter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output) { - ; // noop -} - -void handleOutputLeave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output) { - ; // noop -} - -void handleState(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *state) { - ; // noop -} - -void handleDone(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { - ; // noop -} - -void handleClosed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { - struct xdpw_screencast_context *ctx = data; - - struct SToplevelEntry *current; - wl_list_for_each(current, &ctx->toplevel_resource_list, link) { - if (current->handle == handle) { - break; - } - } - - logprint(DEBUG, "hyprland: toplevel closed %s", current->clazz); - - wl_list_remove(¤t->link); -} - -void handleParent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct zwlr_foreign_toplevel_handle_v1 *parent) { - ; // noop -} - -struct zwlr_foreign_toplevel_handle_v1_listener toplevelHandleListener = { - .title = handleTitle, - .app_id = handleAppID, - .output_enter = handleOutputEnter, - .output_leave = handleOutputLeave, - .state = handleState, - .done = handleDone, - .closed = handleClosed, - .parent = handleParent, -}; - -void handleToplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, struct zwlr_foreign_toplevel_handle_v1 *toplevel) { - struct xdpw_screencast_context *ctx = data; - - struct SToplevelEntry *entry = malloc(sizeof(struct SToplevelEntry)); - - entry->handle = toplevel; - - wl_list_insert(&ctx->toplevel_resource_list, &entry->link); - - zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &toplevelHandleListener, ctx); - - logprint(DEBUG, "hyprland: toplevel handle created %lx", toplevel); -} - -void handleFinished(void *data, struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1) { - logprint(ERROR, "hyprland: compositor called finished on zwlr_foreign_toplevel_manager_v1!"); -} - -struct zwlr_foreign_toplevel_manager_v1_listener toplevelListener = { - .toplevel = handleToplevel, - .finished = handleFinished, -}; - -struct SToplevelEntry *toplevelEntryFromID(struct xdpw_screencast_context *ctx, uint32_t id) { - struct SToplevelEntry *current; - wl_list_for_each(current, &ctx->toplevel_resource_list, link) { - if (((uint64_t)current->handle & 0xFFFFFFFF) == id) { - return current; - } - } - return NULL; -} - -/// - -void wlr_frame_free(struct xdpw_screencast_instance *cast) { - if (!cast->wlr_frame) { - return; - } - zwlr_screencopy_frame_v1_destroy(cast->wlr_frame); - cast->wlr_frame = NULL; - logprint(TRACE, "wlroots: frame destroyed"); -} - -void xdpw_wlr_frame_finish(struct xdpw_screencast_instance *cast) { - logprint(TRACE, "wlroots: finish screencopy"); - - wlr_frame_free(cast); - - if (cast->quit || cast->err) { - // TODO: revisit the exit condition (remove quit?) - // and clean up sessions that still exist if err - // is the cause of the instance_destroy call - xdpw_screencast_instance_destroy(cast); - return; - } - - if (!cast->pwr_stream_state) { - cast->frame_state = XDPW_FRAME_STATE_NONE; - return; - } - - if (cast->frame_state == XDPW_FRAME_STATE_RENEG) { - pwr_update_stream_param(cast); - } - - if (cast->frame_state == XDPW_FRAME_STATE_FAILED) { - xdpw_pwr_enqueue_buffer(cast); - } - - if (cast->frame_state == XDPW_FRAME_STATE_SUCCESS) { - xdpw_pwr_enqueue_buffer(cast); - uint64_t delay_ns = fps_limit_measure_end(&cast->fps_limit, cast->framerate); - if (delay_ns > 0) { - xdpw_add_timer(cast->ctx->state, delay_ns, (xdpw_event_loop_timer_func_t)xdpw_wlr_frame_start, cast); - return; - } - } - xdpw_wlr_frame_start(cast); -} - -void xdpw_wlr_frame_start(struct xdpw_screencast_instance *cast) { - logprint(TRACE, "wlroots: start screencopy"); - if (cast->quit || cast->err) { - xdpw_screencast_instance_destroy(cast); - return; - } - - if (cast->initialized && !cast->pwr_stream_state) { - cast->frame_state = XDPW_FRAME_STATE_NONE; - return; - } - - cast->frame_state = XDPW_FRAME_STATE_STARTED; - xdpw_wlr_register_cb(cast); -} - -static void wlr_frame_buffer_done(void *data, struct zwlr_screencopy_frame_v1 *frame); - -static void wlr_frame_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "wlroots: buffer event handler"); - cast->wlr_frame = frame; - - cast->screencopy_frame_info[WL_SHM].width = width; - cast->screencopy_frame_info[WL_SHM].height = height; - cast->screencopy_frame_info[WL_SHM].stride = stride; - cast->screencopy_frame_info[WL_SHM].size = stride * height; - cast->screencopy_frame_info[WL_SHM].format = xdpw_format_drm_fourcc_from_wl_shm(format); - - if (zwlr_screencopy_manager_v1_get_version(cast->ctx->screencopy_manager) < 3) { - wlr_frame_buffer_done(cast, frame); - } -} - -static void wlr_frame_linux_dmabuf(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "wlroots: linux_dmabuf event handler"); - - cast->screencopy_frame_info[DMABUF].width = width; - cast->screencopy_frame_info[DMABUF].height = height; - cast->screencopy_frame_info[DMABUF].format = format; -} - -static void wlr_frame_buffer_done(void *data, struct zwlr_screencopy_frame_v1 *frame) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "wlroots: buffer_done event handler"); - - if (!cast->initialized) { - xdpw_wlr_frame_finish(cast); - return; - } - - // Check if announced screencopy information is compatible with pipewire meta - if ((cast->pwr_format.format != xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[cast->buffer_type].format) && - cast->pwr_format.format != - xdpw_format_pw_strip_alpha(xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[cast->buffer_type].format))) || - cast->pwr_format.size.width != cast->screencopy_frame_info[cast->buffer_type].width || - cast->pwr_format.size.height != cast->screencopy_frame_info[cast->buffer_type].height) { - logprint(DEBUG, "wlroots: pipewire and wlroots metadata are incompatible. Renegotiate stream"); - cast->frame_state = XDPW_FRAME_STATE_RENEG; - xdpw_wlr_frame_finish(cast); - return; - } - - if (!cast->current_frame.xdpw_buffer) { - xdpw_pwr_dequeue_buffer(cast); - } - - if (!cast->current_frame.xdpw_buffer) { - logprint(WARN, "wlroots: no current buffer"); - xdpw_wlr_frame_finish(cast); - return; - } - - assert(cast->current_frame.xdpw_buffer); - - // Check if dequeued buffer is compatible with announced buffer - if ((cast->buffer_type == WL_SHM && (cast->current_frame.xdpw_buffer->size[0] != cast->screencopy_frame_info[cast->buffer_type].size || - cast->current_frame.xdpw_buffer->stride[0] != cast->screencopy_frame_info[cast->buffer_type].stride)) || - cast->current_frame.xdpw_buffer->width != cast->screencopy_frame_info[cast->buffer_type].width || - cast->current_frame.xdpw_buffer->height != cast->screencopy_frame_info[cast->buffer_type].height) { - logprint(DEBUG, "wlroots: pipewire buffer has wrong dimensions"); - cast->frame_state = XDPW_FRAME_STATE_FAILED; - xdpw_wlr_frame_finish(cast); - return; - } - - cast->current_frame.damage_count = 0; - zwlr_screencopy_frame_v1_copy_with_damage(frame, cast->current_frame.xdpw_buffer->buffer); - logprint(TRACE, "wlroots: frame copied"); - - fps_limit_measure_start(&cast->fps_limit, cast->framerate); -} - -static void wlr_frame_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "wlroots: flags event handler"); - cast->current_frame.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; -} - -static void wlr_frame_damage(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "wlroots: damage event handler"); - - logprint(TRACE, "wlroots: damage %"PRIu32": %"PRIu32",%"PRIu32"x%"PRIu32",%"PRIu32, cast->current_frame.damage_count, x, y, width, height); - struct xdpw_frame_damage damage = {x, y, width, height}; - if (cast->current_frame.damage_count < 4) { - cast->current_frame.damage[cast->current_frame.damage_count++] = damage; - } else { - cast->current_frame.damage[3] = merge_damage(&cast->current_frame.damage[3], &damage); - } -} - -static void wlr_frame_ready(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "wlroots: ready event handler"); - - cast->current_frame.tv_sec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo); - cast->current_frame.tv_nsec = tv_nsec; - logprint(TRACE, "wlroots: timestamp %" PRIu64 ":%" PRIu32, cast->current_frame.tv_sec, cast->current_frame.tv_nsec); - - cast->frame_state = XDPW_FRAME_STATE_SUCCESS; - - xdpw_wlr_frame_finish(cast); -} - -static void wlr_frame_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "wlroots: failed event handler"); - - cast->frame_state = XDPW_FRAME_STATE_FAILED; - - xdpw_wlr_frame_finish(cast); -} - -static const struct zwlr_screencopy_frame_v1_listener wlr_frame_listener = { - .buffer = wlr_frame_buffer, - .buffer_done = wlr_frame_buffer_done, - .linux_dmabuf = wlr_frame_linux_dmabuf, - .flags = wlr_frame_flags, - .ready = wlr_frame_ready, - .failed = wlr_frame_failed, - .damage = wlr_frame_damage, -}; - -static void hyprland_frame_buffer_done(void *data, struct hyprland_toplevel_export_frame_v1 *frame); - -static void hyprland_frame_buffer(void *data, struct hyprland_toplevel_export_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height, - uint32_t stride) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "hyprland: buffer event handler"); - cast->hyprland_frame = frame; - - cast->screencopy_frame_info[WL_SHM].width = width; - cast->screencopy_frame_info[WL_SHM].height = height; - cast->screencopy_frame_info[WL_SHM].stride = stride; - cast->screencopy_frame_info[WL_SHM].size = stride * height; - cast->screencopy_frame_info[WL_SHM].format = xdpw_format_drm_fourcc_from_wl_shm(format); -} - -static void hyprland_frame_linux_dmabuf(void *data, struct hyprland_toplevel_export_frame_v1 *frame, uint32_t format, uint32_t width, - uint32_t height) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "hyprland: linux_dmabuf event handler"); - - cast->screencopy_frame_info[DMABUF].width = width; - cast->screencopy_frame_info[DMABUF].height = height; - cast->screencopy_frame_info[DMABUF].format = format; -} - -static void hyprland_frame_buffer_done(void *data, struct hyprland_toplevel_export_frame_v1 *frame) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "hyprland: buffer_done event handler"); - - if (!cast->initialized) { - xdpw_wlr_frame_finish(cast); - return; - } - - // Check if announced screencopy information is compatible with pipewire meta - if ((cast->pwr_format.format != xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[cast->buffer_type].format) && - cast->pwr_format.format != - xdpw_format_pw_strip_alpha(xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[cast->buffer_type].format))) || - cast->pwr_format.size.width != cast->screencopy_frame_info[cast->buffer_type].width || - cast->pwr_format.size.height != cast->screencopy_frame_info[cast->buffer_type].height) { - logprint(DEBUG, "hyprland: pipewire and wlroots metadata are incompatible. Renegotiate stream"); - cast->frame_state = XDPW_FRAME_STATE_RENEG; - xdpw_wlr_frame_finish(cast); - return; - } - - if (!cast->current_frame.xdpw_buffer) { - xdpw_pwr_dequeue_buffer(cast); - } - - if (!cast->current_frame.xdpw_buffer) { - logprint(WARN, "hyprland: no current buffer"); - xdpw_wlr_frame_finish(cast); - return; - } - - assert(cast->current_frame.xdpw_buffer); - - // Check if dequeued buffer is compatible with announced buffer - if ((cast->buffer_type == WL_SHM && (cast->current_frame.xdpw_buffer->size[0] != cast->screencopy_frame_info[cast->buffer_type].size || - cast->current_frame.xdpw_buffer->stride[0] != cast->screencopy_frame_info[cast->buffer_type].stride)) || - cast->current_frame.xdpw_buffer->width != cast->screencopy_frame_info[cast->buffer_type].width || - cast->current_frame.xdpw_buffer->height != cast->screencopy_frame_info[cast->buffer_type].height) { - logprint(DEBUG, "hyprland: pipewire buffer has wrong dimensions"); - cast->frame_state = XDPW_FRAME_STATE_FAILED; - xdpw_wlr_frame_finish(cast); - return; - } - - hyprland_toplevel_export_frame_v1_copy(frame, cast->current_frame.xdpw_buffer->buffer, 0); - logprint(TRACE, "hyprland: frame copied"); - - fps_limit_measure_start(&cast->fps_limit, cast->framerate); -} - -static void hyprland_frame_flags(void *data, struct hyprland_toplevel_export_frame_v1 *frame, uint32_t flags) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "hyprland: flags event handler"); - cast->current_frame.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; -} - -static void hyprland_frame_damage(void *data, struct hyprland_toplevel_export_frame_v1 *frame, uint32_t x, uint32_t y, uint32_t width, - uint32_t height) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "hyprland: damage event handler"); - - logprint(TRACE, "hyprland: damage %"PRIu32": %"PRIu32",%"PRIu32"x%"PRIu32",%"PRIu32, cast->current_frame.damage_count, x, y, width, height); - struct xdpw_frame_damage damage = {x, y, width, height}; - if (cast->current_frame.damage_count < 4) { - cast->current_frame.damage[cast->current_frame.damage_count++] = damage; - } else { - cast->current_frame.damage[3] = merge_damage(&cast->current_frame.damage[3], &damage); - } -} - -static void hyprland_frame_ready(void *data, struct hyprland_toplevel_export_frame_v1 *frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, - uint32_t tv_nsec) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "hyprland: ready event handler"); - - cast->current_frame.tv_sec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo); - cast->current_frame.tv_nsec = tv_nsec; - logprint(TRACE, "hyprland: timestamp %" PRIu64 ":%" PRIu32, cast->current_frame.tv_sec, cast->current_frame.tv_nsec); - - cast->frame_state = XDPW_FRAME_STATE_SUCCESS; - - xdpw_wlr_frame_finish(cast); -} - -static void hyprland_frame_failed(void *data, struct hyprland_toplevel_export_frame_v1 *frame) { - struct xdpw_screencast_instance *cast = data; - if (!frame) { - return; - } - - logprint(TRACE, "hyprland: failed event handler"); - - cast->frame_state = XDPW_FRAME_STATE_FAILED; - - xdpw_wlr_frame_finish(cast); -} - -static const struct hyprland_toplevel_export_frame_v1_listener hyprland_frame_listener = {.buffer = hyprland_frame_buffer, - .buffer_done = hyprland_frame_buffer_done, - .linux_dmabuf = hyprland_frame_linux_dmabuf, - .flags = hyprland_frame_flags, - .ready = hyprland_frame_ready, - .failed = hyprland_frame_failed, - .damage = hyprland_frame_damage}; - -void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast) { - if (cast->target.x != -1 && cast->target.y != -1 && cast->target.w != -1 && cast->target.h != -1 && cast->target.window_handle <= 0) { - // capture region - cast->frame_callback = - zwlr_screencopy_manager_v1_capture_output_region(cast->ctx->screencopy_manager, cast->with_cursor, cast->target.output->output, - cast->target.x, cast->target.y, cast->target.w, cast->target.h); - } else if (cast->target.window_handle == -1) { - cast->frame_callback = - zwlr_screencopy_manager_v1_capture_output(cast->ctx->screencopy_manager, cast->with_cursor, cast->target.output->output); - } else { - // share window - struct SToplevelEntry *entry = toplevelEntryFromID(cast->ctx, cast->target.window_handle); - - if (!entry) { - logprint(DEBUG, "hyprland: error in getting entry"); - return; - } - - cast->frame_callback_hyprland = hyprland_toplevel_export_manager_v1_capture_toplevel_with_wlr_toplevel_handle( - cast->ctx->hyprland_toplevel_manager, cast->with_cursor, entry->handle); - - hyprland_toplevel_export_frame_v1_add_listener(cast->frame_callback_hyprland, &hyprland_frame_listener, cast); - - logprint(TRACE, "hyprland: callbacks registered"); - return; - } - - zwlr_screencopy_frame_v1_add_listener(cast->frame_callback, &wlr_frame_listener, cast); - logprint(TRACE, "wlroots: callbacks registered"); -} - -static void wlr_output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, - int32_t subpixel, const char *make, const char *model, int32_t transform) { - struct xdpw_wlr_output *output = data; - output->make = strdup(make); - output->model = strdup(model); - output->transform = transform; -} - -static void wlr_output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { - if (flags & WL_OUTPUT_MODE_CURRENT) { - struct xdpw_wlr_output *output = data; - output->framerate = (float)refresh / 1000; - } -} - -static void wlr_output_handle_done(void *data, struct wl_output *wl_output) { /* Nothing to do */ -} - -static void wlr_output_handle_scale(void *data, struct wl_output *wl_output, int32_t factor) { /* Nothing to do */ -} - -static const struct wl_output_listener wlr_output_listener = { - .geometry = wlr_output_handle_geometry, - .mode = wlr_output_handle_mode, - .done = wlr_output_handle_done, - .scale = wlr_output_handle_scale, -}; - -static void wlr_xdg_output_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { - struct xdpw_wlr_output *output = data; - - output->name = strdup(name); - - logprint(INFO, "Output %lx name: %s", data, name); -}; - -static void noop() { - // This space intentionally left blank -} - -static const struct zxdg_output_v1_listener wlr_xdg_output_listener = { - .logical_position = noop, - .logical_size = noop, - .done = NULL, /* Deprecated */ - .description = noop, - .name = wlr_xdg_output_name, -}; - -static void wlr_add_xdg_output_listener(struct xdpw_wlr_output *output, struct zxdg_output_v1 *xdg_output) { - output->xdg_output = xdg_output; - zxdg_output_v1_add_listener(output->xdg_output, &wlr_xdg_output_listener, output); -} - -static void wlr_init_xdg_output(struct xdpw_screencast_context *ctx, struct xdpw_wlr_output *output) { - struct zxdg_output_v1 *xdg_output = zxdg_output_manager_v1_get_xdg_output(ctx->xdg_output_manager, output->output); - wlr_add_xdg_output_listener(output, xdg_output); -} - -static void wlr_init_xdg_outputs(struct xdpw_screencast_context *ctx) { - struct xdpw_wlr_output *output, *tmp; - wl_list_for_each_safe(output, tmp, &ctx->output_list, link) { - if (output->xdg_output) { - continue; - } - wlr_init_xdg_output(ctx, output); - } -} - -char *buildWindowList(struct xdpw_screencast_context *ctx) { - char *rolling = calloc(1, 1); - - if (!ctx->wlroots_toplevel_manager) return rolling; - - struct SToplevelEntry *current; - wl_list_for_each(current, &ctx->toplevel_resource_list, link) { - char *oldRolling = rolling; - - rolling = getFormat("%s%u[HC>]%s[HT>]%s[HE>]", rolling, (uint32_t)(((uint64_t)current->handle) & 0xFFFFFFFF), current->clazz, current->name); - - free(oldRolling); - } - - for (size_t i = 0; i < strlen(rolling); ++i) { - if (rolling[i] == '\"') rolling[i] = ' '; - } - - return rolling; -} - -struct xdpw_share xdpw_wlr_chooser(struct xdpw_screencast_context *ctx) { - char result[1024] = {0}; - FILE *fp; - char buf[1024] = {0}; - - const char *WAYLAND_DISPLAY = getenv("WAYLAND_DISPLAY"); - const char *XCURSOR_SIZE = getenv("XCURSOR_SIZE"); - const char *HYPRLAND_INSTANCE_SIGNATURE = getenv("HYPRLAND_INSTANCE_SIGNATURE"); - - char *windowList = buildWindowList(ctx); - - char *cmd = getFormat( - "WAYLAND_DISPLAY=%s QT_QPA_PLATFORM=\"wayland\" XCURSOR_SIZE=%s HYPRLAND_INSTANCE_SIGNATURE=%s XDPH_WINDOW_SHARING_LIST=\"%s\" " - "hyprland-share-picker", - WAYLAND_DISPLAY, XCURSOR_SIZE ? XCURSOR_SIZE : "24", HYPRLAND_INSTANCE_SIGNATURE ? HYPRLAND_INSTANCE_SIGNATURE : "0", windowList); - - free(windowList); - - logprint(DEBUG, "Screencast: Picker: Running command \"%s\"", cmd); - - fp = popen(cmd, "r"); - if (fp == NULL) { - printf("Failed to run command\n"); - exit(1); - } - - while (fgets(buf, sizeof(buf), fp) != NULL) { - strcat(result, buf); - } - - pclose(fp); - - free(cmd); - - // great, let's parse it. - - struct xdpw_share res = {NULL, -1, -1, -1, -1, -1}; - - if (strncmp(result, "screen:", 7) == 0) { - // find output - logprint(DEBUG, "Screencast: Attempting to find screen for %s", result); - - char *display_name = malloc(strlen(result) - 7); - strncpy(display_name, result + 7, strlen(result) - 8); - display_name[strlen(result) - 8] = 0; - - struct xdpw_wlr_output *out; - bool found = false; - wl_list_for_each(out, &ctx->output_list, link) { - if (strcmp(out->name, display_name) == 0) { - found = true; - break; - } - } - - free(display_name); - - if (!found) return res; - - res.output = out; - return res; - } else if (strncmp(result, "region:", 7) == 0) { - // find output - logprint(DEBUG, "Screencast: Attempting to find region for %s", result); - - int atPos = 7; - for (int i = 7; i < (int)strlen(result); ++i) { - if (result[i] == '@') { - atPos = i; - break; - } - } - - char *display_name = malloc(atPos - 6); - strncpy(display_name, result + 7, atPos - 7); - display_name[atPos - 7] = 0; - - struct xdpw_wlr_output *out; - wl_list_for_each(out, &ctx->output_list, link) { - if (strcmp(out->name, display_name) == 0) { - break; - } - } - - // then get coords - int coordno = 0; - int coords[4] = {-1, -1, -1, -1}; - int coordbegin = 7 + strlen(display_name) + 1; - for (int i = 7 + strlen(display_name) + 1; i < (int)strlen(result); ++i) { - if (result[i] == ',' || result[i] == '@' || i + 1 == (int)strlen(result)) { - char *entire = malloc(i - coordbegin + 1); - strncpy(entire, result + coordbegin, i - coordbegin); - entire[i - coordbegin] = 0; - coords[coordno] = strtol(entire, NULL, 10); - free(entire); - - coordno++; - coordbegin = i + 1; - i++; - } - } - - free(display_name); - - struct xdpw_share res2 = {out, coords[0], coords[1], coords[2], coords[3]}; - return res2; - } else if (strncmp(result, "window:", 7) == 0) { - if (ctx->hyprland_toplevel_manager == NULL) { - logprint(DEBUG, "Screencast: Window sharing attempted but the toplevel protocol is not implemented by the compositor!"); - return res; - } - - logprint(DEBUG, "Screencast: Attempting to find window for %s", result); - - char *display_name = malloc(strlen(result) - 7); - strncpy(display_name, result + 7, strlen(result) - 8); - display_name[strlen(result) - 8] = 0; - - res.window_handle = strtol(display_name, NULL, 10); - - free(display_name); - return res; - } else { - logprint(DEBUG, "Screencast: Invalid result from hyprland-share-picker: %s", result); - return res; - } - - return res; -} - -struct xdpw_wlr_output *xdpw_wlr_output_first(struct wl_list *output_list) { - struct xdpw_wlr_output *output, *tmp; - wl_list_for_each_safe(output, tmp, output_list, link) { return output; } - return NULL; -} - -struct xdpw_wlr_output *xdpw_wlr_output_find_by_name(struct wl_list *output_list, const char *name) { - struct xdpw_wlr_output *output, *tmp; - wl_list_for_each_safe(output, tmp, output_list, link) { - if (strcmp(output->name, name) == 0) { - return output; - } - } - return NULL; -} - -struct xdpw_wlr_output *xdpw_wlr_output_find(struct xdpw_screencast_context *ctx, struct wl_output *out, uint32_t id) { - struct xdpw_wlr_output *output, *tmp; - wl_list_for_each_safe(output, tmp, &ctx->output_list, link) { - if ((output->output == out) || (output->id == id)) { - return output; - } - } - return NULL; -} - -static void wlr_remove_output(struct xdpw_wlr_output *out) { - free(out->name); - free(out->make); - free(out->model); - zxdg_output_v1_destroy(out->xdg_output); - wl_output_destroy(out->output); - wl_list_remove(&out->link); - free(out); -} - -static void wlr_format_modifier_pair_add(struct xdpw_screencast_context *ctx, uint32_t format, uint64_t modifier) { - struct xdpw_format_modifier_pair *fm_pair; - wl_array_for_each(fm_pair, &ctx->format_modifier_pairs) { - if (fm_pair->fourcc == format && fm_pair->modifier == modifier) { - logprint(TRACE, "wlroots: skipping duplicated format %u (%lu)", fm_pair->fourcc, fm_pair->modifier); - return; - } - } - - fm_pair = wl_array_add(&ctx->format_modifier_pairs, sizeof(struct xdpw_format_modifier_pair)); - fm_pair->fourcc = format; - fm_pair->modifier = modifier; - logprint(TRACE, "wlroots: format %u (%lu)", fm_pair->fourcc, fm_pair->modifier); -} - -static void linux_dmabuf_handle_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, - uint32_t modifier_lo) { - struct xdpw_screencast_context *ctx = data; - - logprint(TRACE, "wlroots: linux_dmabuf_handle_modifier called"); - - uint64_t modifier = (((uint64_t)modifier_hi) << 32) | modifier_lo; - wlr_format_modifier_pair_add(ctx, format, modifier); -} - -static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = { - .format = noop, - .modifier = linux_dmabuf_handle_modifier, -}; - -static void linux_dmabuf_feedback_handle_main_device(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, - struct wl_array *device_arr) { - struct xdpw_screencast_context *ctx = data; - - logprint(DEBUG, "wlroots: linux_dmabuf_feedback_handle_main_device called"); - - assert(ctx->gbm == NULL); - - dev_t device; - assert(device_arr->size == sizeof(device)); - memcpy(&device, device_arr->data, sizeof(device)); - - drmDevice *drmDev; - if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { - logprint(WARN, "wlroots: unable to open main device"); - ctx->state->config->screencast_conf.force_mod_linear = true; - return; - } - ctx->gbm = xdpw_gbm_device_create(drmDev); -} - -static void linux_dmabuf_feedback_format_table(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, int fd, uint32_t size) { - struct xdpw_screencast_context *ctx = data; - - logprint(DEBUG, "wlroots: linux_dmabuf_feedback_format_table called"); - - wl_array_release(&ctx->format_modifier_pairs); - wl_array_init(&ctx->format_modifier_pairs); - - ctx->feedback_data.format_table_data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (ctx->feedback_data.format_table_data == MAP_FAILED) { - ctx->feedback_data.format_table_data = NULL; - ctx->feedback_data.format_table_size = 0; - return; - } - ctx->feedback_data.format_table_size = size; -} - -static void linux_dmabuf_feedback_handle_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) { - struct xdpw_screencast_context *ctx = data; - - logprint(DEBUG, "wlroots: linux_dmabuf_feedback_handle_done called"); - - if (ctx->feedback_data.format_table_data) { - munmap(ctx->feedback_data.format_table_data, ctx->feedback_data.format_table_size); - } - ctx->feedback_data.format_table_data = NULL; - ctx->feedback_data.format_table_size = 0; -} - -static void linux_dmabuf_feedback_tranche_target_devices(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, - struct wl_array *device_arr) { - struct xdpw_screencast_context *ctx = data; - - logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_target_devices called"); - - dev_t device; - assert(device_arr->size == sizeof(device)); - memcpy(&device, device_arr->data, sizeof(device)); - - drmDevice *drmDev; - if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { - return; - } - - if (ctx->gbm) { - drmDevice *drmDevRenderer = NULL; - drmGetDevice2(gbm_device_get_fd(ctx->gbm), /* flags */ 0, &drmDevRenderer); - ctx->feedback_data.device_used = drmDevicesEqual(drmDevRenderer, drmDev); - } else { - ctx->gbm = xdpw_gbm_device_create(drmDev); - ctx->feedback_data.device_used = ctx->gbm != NULL; - } -} - -static void linux_dmabuf_feedback_tranche_flags(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, uint32_t flags) { - logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_flags called"); -} - -static void linux_dmabuf_feedback_tranche_formats(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, - struct wl_array *indices) { - struct xdpw_screencast_context *ctx = data; - - logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_formats called"); - - if (!ctx->feedback_data.device_used || !ctx->feedback_data.format_table_data) { - return; - } - struct fm_entry { - uint32_t format; - uint32_t padding; - uint64_t modifier; - }; - // An entry in the table has to be 16 bytes long - assert(sizeof(struct fm_entry) == 16); - - uint32_t n_modifiers = ctx->feedback_data.format_table_size / sizeof(struct fm_entry); - struct fm_entry *fm_entry = ctx->feedback_data.format_table_data; - uint16_t *idx; - wl_array_for_each(idx, indices) { - if (*idx >= n_modifiers) { - continue; - } - wlr_format_modifier_pair_add(ctx, (fm_entry + *idx)->format, (fm_entry + *idx)->modifier); - } -} - -static void linux_dmabuf_feedback_tranche_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) { - struct xdpw_screencast_context *ctx = data; - - logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_done called"); - - ctx->feedback_data.device_used = false; -} - -static const struct zwp_linux_dmabuf_feedback_v1_listener linux_dmabuf_listener_feedback = { - .main_device = linux_dmabuf_feedback_handle_main_device, - .format_table = linux_dmabuf_feedback_format_table, - .done = linux_dmabuf_feedback_handle_done, - .tranche_target_device = linux_dmabuf_feedback_tranche_target_devices, - .tranche_flags = linux_dmabuf_feedback_tranche_flags, - .tranche_formats = linux_dmabuf_feedback_tranche_formats, - .tranche_done = linux_dmabuf_feedback_tranche_done, -}; - -static void wlr_registry_handle_add(void *data, struct wl_registry *reg, uint32_t id, const char *interface, uint32_t ver) { - struct xdpw_screencast_context *ctx = data; - - logprint(DEBUG, "wlroots: interface to register %s (Version: %u)", interface, ver); - if (!strcmp(interface, wl_output_interface.name)) { - struct xdpw_wlr_output *output = calloc(1, sizeof(*output)); - - output->id = id; - logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, WL_OUTPUT_VERSION); - output->output = wl_registry_bind(reg, id, &wl_output_interface, WL_OUTPUT_VERSION); - - wl_output_add_listener(output->output, &wlr_output_listener, output); - wl_list_insert(&ctx->output_list, &output->link); - if (ctx->xdg_output_manager) { - wlr_init_xdg_output(ctx, output); - } - } - - if (!strcmp(interface, zwlr_screencopy_manager_v1_interface.name)) { - uint32_t version = ver; - if (SC_MANAGER_VERSION < ver) { - version = SC_MANAGER_VERSION; - } else if (ver < SC_MANAGER_VERSION_MIN) { - version = SC_MANAGER_VERSION_MIN; - } - logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, version); - ctx->screencopy_manager = wl_registry_bind(reg, id, &zwlr_screencopy_manager_v1_interface, version); - } - - if (!strcmp(interface, hyprland_toplevel_export_manager_v1_interface.name) && !ctx->hyprland_toplevel_manager) { - uint32_t version = ver; - - logprint(DEBUG, "hyprland: |-- registered to interface %s (Version %u)", interface, version); - - ctx->hyprland_toplevel_manager = wl_registry_bind(reg, id, &hyprland_toplevel_export_manager_v1_interface, version); - } - - if (!strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) && !ctx->wlroots_toplevel_manager) { - uint32_t version = ver; - - logprint(DEBUG, "hyprland: |-- registered to interface %s (Version %u)", interface, version); - - ctx->wlroots_toplevel_manager = wl_registry_bind(reg, id, &zwlr_foreign_toplevel_manager_v1_interface, version); - wl_list_init(&ctx->toplevel_resource_list); - - zwlr_foreign_toplevel_manager_v1_add_listener(ctx->wlroots_toplevel_manager, &toplevelListener, ctx); - } - - if (strcmp(interface, wl_shm_interface.name) == 0) { - logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, WL_SHM_VERSION); - ctx->shm = wl_registry_bind(reg, id, &wl_shm_interface, WL_SHM_VERSION); - } - - if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { - logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, XDG_OUTPUT_MANAGER_VERSION); - ctx->xdg_output_manager = wl_registry_bind(reg, id, &zxdg_output_manager_v1_interface, XDG_OUTPUT_MANAGER_VERSION); - } - if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) { - uint32_t version = ver; - if (LINUX_DMABUF_VERSION < ver) { - version = LINUX_DMABUF_VERSION; - } else if (LINUX_DMABUF_VERSION_MIN > ver) { - logprint(INFO, "wlroots: interface %s (Version %u) is required for DMA-BUF screencast", interface, LINUX_DMABUF_VERSION_MIN); - return; - } - logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, version); - ctx->linux_dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, version); - - if (version >= 4) { - ctx->linux_dmabuf_feedback = zwp_linux_dmabuf_v1_get_default_feedback(ctx->linux_dmabuf); - zwp_linux_dmabuf_feedback_v1_add_listener(ctx->linux_dmabuf_feedback, &linux_dmabuf_listener_feedback, ctx); - } else { - zwp_linux_dmabuf_v1_add_listener(ctx->linux_dmabuf, &linux_dmabuf_listener, ctx); - } - } -} - -static void wlr_registry_handle_remove(void *data, struct wl_registry *reg, uint32_t id) { - struct xdpw_screencast_context *ctx = data; - struct xdpw_wlr_output *output = xdpw_wlr_output_find(ctx, NULL, id); - if (output) { - logprint(DEBUG, "wlroots: output removed (%s)", output->name); - struct xdpw_screencast_instance *cast, *tmp; - wl_list_for_each_safe(cast, tmp, &ctx->screencast_instances, link) { - if (cast->target.output == output) { - // screencopy might be in process for this instance - wlr_frame_free(cast); - // instance might be waiting for wakeup by the frame limiter - struct xdpw_timer *timer, *ttmp; - wl_list_for_each_safe(timer, ttmp, &cast->ctx->state->timers, link) { - if (timer->user_data == cast) { - xdpw_destroy_timer(timer); - } - } - cast->teardown = true; - xdpw_screencast_instance_teardown(cast); - } - } - wlr_remove_output(output); - } -} - -static const struct wl_registry_listener wlr_registry_listener = { - .global = wlr_registry_handle_add, - .global_remove = wlr_registry_handle_remove, -}; - -int xdpw_wlr_screencopy_init(struct xdpw_state *state) { - struct xdpw_screencast_context *ctx = &state->screencast; - - // initialize a list of outputs - wl_list_init(&ctx->output_list); - - // initialize a list of active screencast instances - wl_list_init(&ctx->screencast_instances); - - // initialize a list of format modifier pairs - wl_array_init(&ctx->format_modifier_pairs); - - // retrieve registry - ctx->registry = wl_display_get_registry(state->wl_display); - wl_registry_add_listener(ctx->registry, &wlr_registry_listener, ctx); - - wl_display_roundtrip(state->wl_display); - - logprint(DEBUG, "wayland: registry listeners run"); - - // make sure our wlroots supports xdg_output_manager - if (!ctx->xdg_output_manager) { - logprint(ERROR, "Compositor doesn't support %s!", zxdg_output_manager_v1_interface.name); - return -1; - } - - wlr_init_xdg_outputs(ctx); - - wl_display_roundtrip(state->wl_display); - - logprint(DEBUG, "wayland: xdg output listeners run"); - - // make sure our wlroots supports shm protocol - if (!ctx->shm) { - logprint(ERROR, "Compositor doesn't support %s!", "wl_shm"); - return -1; - } - - // make sure our wlroots supports screencopy protocol - if (!ctx->screencopy_manager) { - logprint(ERROR, "Compositor doesn't support %s!", zwlr_screencopy_manager_v1_interface.name); - return -1; - } - - // make sure we have a gbm device - if (ctx->linux_dmabuf && !ctx->gbm) { - ctx->gbm = xdpw_gbm_device_create(NULL); - if (!ctx->gbm) { - logprint(ERROR, "System doesn't support gbm!"); - } - } - - return 0; -} - -void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx) { - wl_array_release(&ctx->format_modifier_pairs); - - struct xdpw_wlr_output *output, *tmp_o; - wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) { - wl_list_remove(&output->link); - zxdg_output_v1_destroy(output->xdg_output); - wl_output_destroy(output->output); - } - - struct xdpw_screencast_instance *cast, *tmp_c; - wl_list_for_each_safe(cast, tmp_c, &ctx->screencast_instances, link) { cast->quit = true; } - - if (ctx->screencopy_manager) { - zwlr_screencopy_manager_v1_destroy(ctx->screencopy_manager); - } - if (ctx->shm) { - wl_shm_destroy(ctx->shm); - } - if (ctx->xdg_output_manager) { - zxdg_output_manager_v1_destroy(ctx->xdg_output_manager); - } - if (ctx->gbm) { - int fd = gbm_device_get_fd(ctx->gbm); - gbm_device_destroy(ctx->gbm); - close(fd); - } - if (ctx->linux_dmabuf_feedback) { - zwp_linux_dmabuf_feedback_v1_destroy(ctx->linux_dmabuf_feedback); - } - if (ctx->linux_dmabuf) { - zwp_linux_dmabuf_v1_destroy(ctx->linux_dmabuf); - } - if (ctx->registry) { - wl_registry_destroy(ctx->registry); - } -} diff --git a/src/screenshot/screenshot.c b/src/screenshot/screenshot.c deleted file mode 100644 index 48c4c00..0000000 --- a/src/screenshot/screenshot.c +++ /dev/null @@ -1,300 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "xdpw.h" -#include "screenshot.h" - -static const char object_path[] = "/org/freedesktop/portal/desktop"; -static const char interface_name[] = "org.freedesktop.impl.portal.Screenshot"; - -static bool exec_screenshooter(const char *path) { - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - return false; - } else if (pid == 0) { - char *const argv[] = { - "grim", - "--", - (char *)path, - NULL, - }; - execvp("grim", argv); - - perror("execvp"); - exit(127); - } - - int stat; - if (waitpid(pid, &stat, 0) < 0) { - perror("waitpid"); - return false; - } - - return stat == 0; -} -static bool exec_screenshooter_interactive(const char *path) { - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - return false; - } else if (pid == 0) { - char cmd[strlen(path) + 25]; - snprintf(cmd, sizeof(cmd), "grim -g \"$(slurp)\" -- %s", path); - execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); - perror("execl"); - exit(127); - } - - int stat; - if (waitpid(pid, &stat, 0) < 0) { - perror("waitpid"); - return false; - } - - return stat == 0; -} - -static int method_screenshot(sd_bus_message *msg, void *data, - sd_bus_error *ret_error) { - int ret = 0; - - bool interactive = false; - - char *handle, *app_id, *parent_window; - ret = sd_bus_message_read(msg, "oss", &handle, &app_id, &parent_window); - if (ret < 0) { - return ret; - } - - ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); - if (ret < 0) { - return ret; - } - char *key; - int inner_ret = 0; - while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { - inner_ret = sd_bus_message_read(msg, "s", &key); - if (inner_ret < 0) { - return inner_ret; - } - - if (strcmp(key, "interactive") == 0) { - int mode; - sd_bus_message_read(msg, "v", "b", &mode); - logprint(DEBUG, "dbus: option interactive: %d", mode); - interactive = mode; - } else if (strcmp(key, "modal") == 0) { - int modal; - sd_bus_message_read(msg, "v", "b", &modal); - logprint(DEBUG, "dbus: option modal: %d", modal); - } else { - logprint(WARN, "dbus: unknown option %s", key); - sd_bus_message_skip(msg, "v"); - } - - inner_ret = sd_bus_message_exit_container(msg); - if (inner_ret < 0) { - return inner_ret; - } - } - if (ret < 0) { - return ret; - } - ret = sd_bus_message_exit_container(msg); - if (ret < 0) { - return ret; - } - - // TODO: cleanup this - struct xdpw_request *req = - xdpw_request_create(sd_bus_message_get_bus(msg), handle); - if (req == NULL) { - return -ENOMEM; - } - - // TODO: choose a better path - const char path[] = "/tmp/out.png"; - if (interactive && !exec_screenshooter_interactive(path)) { - return -1; - } - if (!interactive && !exec_screenshooter(path)) { - return -1; - } - - const char uri_prefix[] = "file://"; - char uri[strlen(path) + strlen(uri_prefix) + 1]; - snprintf(uri, sizeof(uri), "%s%s", uri_prefix, path); - - sd_bus_message *reply = NULL; - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - - ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1, "uri", "s", uri); - if (ret < 0) { - return ret; - } - - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - - sd_bus_message_unref(reply); - return 0; -} - -static bool spawn_chooser(int chooser_out[2]) { - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - return false; - } else if (pid == 0) { - close(chooser_out[0]); - - dup2(chooser_out[1], STDOUT_FILENO); - close(chooser_out[1]); - - execl("/bin/sh", "/bin/sh", "-c", "grim -g \"$(slurp -p)\" -t ppm -", NULL); - - perror("execl"); - _exit(127); - } - - int stat; - if (waitpid(pid, &stat, 0) < 0) { - perror("waitpid"); - return false; - } - - close(chooser_out[1]); - return stat == 0; -} - -static bool exec_color_picker(struct xdpw_ppm_pixel *pixel) { - int chooser_out[2]; - if (pipe(chooser_out) == -1) { - perror("pipe chooser_out"); - return false; - } - - if (!spawn_chooser(chooser_out)) { - logprint(ERROR, "Selection failed"); - close(chooser_out[0]); - return false; - } - - FILE *f = fdopen(chooser_out[0], "r"); - if (f == NULL) { - perror("fopen pipe chooser_out"); - close(chooser_out[0]); - return false; - } - - char *format = NULL; - size_t len = 1; - if (getline(&format, &len, f) < 0) { - goto error_img; - } - if (strcmp(format, "P6\n") != 0) { - goto error_img; - } - if (getline(&format, &len, f) < 0) { - goto error_img; - } - if (strcmp(format, "1 1\n") != 0) { - goto error_img; - } - - if (fscanf(f, "%d\n", &pixel->max_color_value) != 1) { - goto error_img; - } - - unsigned char pixels[3]; - if (fread(pixels, 3, 1, f) != 1) { - goto error_img; - } - - pixel->red = pixels[0]; - pixel->green = pixels[1]; - pixel->blue = pixels[2]; - - free(format); - fclose(f); - - return true; - -error_img: - logprint(WARN, "Invalid image format or size"); - free(format); - fclose(f); - return false; -} - -static int method_pick_color(sd_bus_message *msg, void *data, - sd_bus_error *ret_error) { - - int ret = 0; - - char *handle, *app_id, *parent_window; - ret = sd_bus_message_read(msg, "oss", &handle, &app_id, &parent_window); - if (ret < 0) { - return ret; - } - - struct xdpw_request *req = - xdpw_request_create(sd_bus_message_get_bus(msg), handle); - if (req == NULL) { - return -ENOMEM; - } - - struct xdpw_ppm_pixel pixel = {0}; - if (!exec_color_picker(&pixel)) { - return -1; - } - - double red = pixel.red / (pixel.max_color_value * 1.0); - double green = pixel.green / (pixel.max_color_value * 1.0); - double blue = pixel.blue / (pixel.max_color_value * 1.0); - - sd_bus_message *reply = NULL; - ret = sd_bus_message_new_method_return(msg, &reply); - if (ret < 0) { - return ret; - } - - ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1, "color", "(ddd)", red, green, blue); - if (ret < 0) { - return ret; - } - - ret = sd_bus_send(NULL, reply, NULL); - if (ret < 0) { - return ret; - } - - sd_bus_message_unref(reply); - return 0; -} - -static const sd_bus_vtable screenshot_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("Screenshot", "ossa{sv}", "ua{sv}", method_screenshot, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("PickColor", "ossa{sv}", "ua{sv}", method_pick_color, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_PROPERTY("version", "u", NULL, - offsetof(struct xdpw_state, screenshot_version), - SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; - -int xdpw_screenshot_init(struct xdpw_state *state) { - // TODO: cleanup - sd_bus_slot *slot = NULL; - return sd_bus_add_object_vtable(state->bus, &slot, object_path, interface_name, - screenshot_vtable, state); -} diff --git a/src/shared/ScreencopyShared.cpp b/src/shared/ScreencopyShared.cpp new file mode 100644 index 0000000..cc22875 --- /dev/null +++ b/src/shared/ScreencopyShared.cpp @@ -0,0 +1,315 @@ +#include "ScreencopyShared.hpp" +#include "../helpers/MiscFunctions.hpp" +#include +#include "../helpers/Log.hpp" +#include +#include +#include "../core/PortalManager.hpp" +#include +#include +#include + +std::string sanitizeNameForWindowList(const std::string& name) { + std::string result = name; + for (size_t i = 1; i < result.size(); ++i) { + if (result[i - 1] == '>' && result[i] == ']') + result[i] = ' '; + if (result[i] == '\"') + result[i] = ' '; + } + return result; +} + +std::string buildWindowList() { + std::string result = ""; + if (!g_pPortalManager->m_sPortals.screencopy->hasToplevelCapabilities()) + return result; + + for (auto& e : g_pPortalManager->m_sHelpers.toplevel->m_vToplevels) { + + result += std::format("{}[HC>]{}[HT>]{}[HE>]", (uint32_t)(((uint64_t)e->handle) & 0xFFFFFFFF), sanitizeNameForWindowList(e->windowClass), + sanitizeNameForWindowList(e->windowTitle)); + } + + return result; +} + +SSelectionData promptForScreencopySelection() { + SSelectionData data; + + const char* WAYLAND_DISPLAY = getenv("WAYLAND_DISPLAY"); + const char* XCURSOR_SIZE = getenv("XCURSOR_SIZE"); + const char* HYPRLAND_INSTANCE_SIGNATURE = getenv("HYPRLAND_INSTANCE_SIGNATURE"); + + std::string cmd = + std::format("WAYLAND_DISPLAY={} QT_QPA_PLATFORM=\"wayland\" XCURSOR_SIZE={} HYPRLAND_INSTANCE_SIGNATURE={} XDPH_WINDOW_SHARING_LIST=\"{}\" hyprland-share-picker", + WAYLAND_DISPLAY ? WAYLAND_DISPLAY : "", XCURSOR_SIZE ? XCURSOR_SIZE : "24", HYPRLAND_INSTANCE_SIGNATURE ? HYPRLAND_INSTANCE_SIGNATURE : "0", buildWindowList()); + + const auto RETVAL = execAndGet(cmd.c_str()); + + Debug::log(LOG, "[sc] Selection: {}", RETVAL); + + const auto FLAGS = RETVAL.substr(0, RETVAL.find_first_of('/')); + const auto SEL = RETVAL.substr(RETVAL.find_first_of('/') + 1); + + for (auto& flag : FLAGS) { + if (flag == 'r') { + data.allowToken = true; + } else { + Debug::log(LOG, "[screencopy] unknown flag from share-picker: {}", flag); + } + } + + if (SEL.find("screen:") == 0) { + data.type = TYPE_OUTPUT; + data.output = SEL.substr(7); + + data.output.pop_back(); + } else if (SEL.find("window:") == 0) { + data.type = TYPE_WINDOW; + uint32_t handleLo = std::stoull(SEL.substr(7)); + data.windowHandle = nullptr; + + for (auto& e : g_pPortalManager->m_sHelpers.toplevel->m_vToplevels) { + uint32_t handleLoE = (uint32_t)(((uint64_t)e->handle) & 0xFFFFFFFF); + + if (handleLoE == handleLo) { + data.windowHandle = e->handle; + break; + } + } + + } else if (SEL.find("region:") == 0) { + std::string running = SEL; + running = running.substr(7); + data.type = TYPE_GEOMETRY; + data.output = running.substr(0, running.find_first_of('@')); + running = running.substr(running.find_first_of('@') + 1); + + data.x = std::stoi(running.substr(0, running.find_first_of(','))); + running = running.substr(running.find_first_of(',') + 1); + data.y = std::stoi(running.substr(0, running.find_first_of(','))); + running = running.substr(running.find_first_of(',') + 1); + data.w = std::stoi(running.substr(0, running.find_first_of(','))); + running = running.substr(running.find_first_of(',') + 1); + data.h = std::stoi(running); + } + + return data; +} + +wl_shm_format wlSHMFromDrmFourcc(uint32_t format) { + switch (format) { + case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888; + case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_NV12: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: return (wl_shm_format)format; + default: Debug::log(ERR, "[screencopy] Unknown format {}", format); exit(1); + } +} + +uint32_t drmFourccFromSHM(wl_shm_format format) { + switch (format) { + case WL_SHM_FORMAT_ARGB8888: return DRM_FORMAT_ARGB8888; + case WL_SHM_FORMAT_XRGB8888: return DRM_FORMAT_XRGB8888; + case WL_SHM_FORMAT_RGBA8888: + case WL_SHM_FORMAT_RGBX8888: + case WL_SHM_FORMAT_ABGR8888: + case WL_SHM_FORMAT_XBGR8888: + case WL_SHM_FORMAT_BGRA8888: + case WL_SHM_FORMAT_BGRX8888: + case WL_SHM_FORMAT_NV12: + case WL_SHM_FORMAT_XRGB2101010: + case WL_SHM_FORMAT_XBGR2101010: + case WL_SHM_FORMAT_RGBX1010102: + case WL_SHM_FORMAT_BGRX1010102: + case WL_SHM_FORMAT_ARGB2101010: + case WL_SHM_FORMAT_ABGR2101010: + case WL_SHM_FORMAT_RGBA1010102: + case WL_SHM_FORMAT_BGRA1010102: return (uint32_t)format; + default: Debug::log(ERR, "[screencopy] Unknown format {}", (int)format); exit(1); + } +} + +spa_video_format pwFromDrmFourcc(uint32_t format) { + switch (format) { + case DRM_FORMAT_ARGB8888: return SPA_VIDEO_FORMAT_BGRA; + case DRM_FORMAT_XRGB8888: return SPA_VIDEO_FORMAT_BGRx; + case DRM_FORMAT_RGBA8888: return SPA_VIDEO_FORMAT_ABGR; + case DRM_FORMAT_RGBX8888: return SPA_VIDEO_FORMAT_xBGR; + case DRM_FORMAT_ABGR8888: return SPA_VIDEO_FORMAT_RGBA; + case DRM_FORMAT_XBGR8888: return SPA_VIDEO_FORMAT_RGBx; + case DRM_FORMAT_BGRA8888: return SPA_VIDEO_FORMAT_ARGB; + case DRM_FORMAT_BGRX8888: return SPA_VIDEO_FORMAT_xRGB; + case DRM_FORMAT_NV12: return SPA_VIDEO_FORMAT_NV12; + case DRM_FORMAT_XRGB2101010: return SPA_VIDEO_FORMAT_xRGB_210LE; + case DRM_FORMAT_XBGR2101010: return SPA_VIDEO_FORMAT_xBGR_210LE; + case DRM_FORMAT_RGBX1010102: return SPA_VIDEO_FORMAT_RGBx_102LE; + case DRM_FORMAT_BGRX1010102: return SPA_VIDEO_FORMAT_BGRx_102LE; + case DRM_FORMAT_ARGB2101010: return SPA_VIDEO_FORMAT_ARGB_210LE; + 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; + default: Debug::log(ERR, "[screencopy] Unknown format {}", (int)format); exit(1); + } +} + +std::string getRandName(std::string prefix) { + std::srand(time(NULL)); + return prefix + + std::format("{}{}{}{}{}{}", (int)(std::rand() % 10), (int)(std::rand() % 10), (int)(std::rand() % 10), (int)(std::rand() % 10), (int)(std::rand() % 10), + (int)(std::rand() % 10)); +} + +spa_video_format pwStripAlpha(spa_video_format format) { + switch (format) { + case SPA_VIDEO_FORMAT_BGRA: return SPA_VIDEO_FORMAT_BGRx; + case SPA_VIDEO_FORMAT_ABGR: return SPA_VIDEO_FORMAT_xBGR; + case SPA_VIDEO_FORMAT_RGBA: return SPA_VIDEO_FORMAT_RGBx; + case SPA_VIDEO_FORMAT_ARGB: return SPA_VIDEO_FORMAT_xRGB; + case SPA_VIDEO_FORMAT_ARGB_210LE: return SPA_VIDEO_FORMAT_xRGB_210LE; + case SPA_VIDEO_FORMAT_ABGR_210LE: return SPA_VIDEO_FORMAT_xBGR_210LE; + case SPA_VIDEO_FORMAT_RGBA_102LE: return SPA_VIDEO_FORMAT_RGBx_102LE; + case SPA_VIDEO_FORMAT_BGRA_102LE: return SPA_VIDEO_FORMAT_BGRx_102LE; + default: return SPA_VIDEO_FORMAT_UNKNOWN; + } +} + +spa_pod* build_buffer(spa_pod_builder* b, uint32_t blocks, uint32_t size, uint32_t stride, uint32_t datatype) { + assert(blocks > 0); + assert(datatype > 0); + spa_pod_frame f[1]; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(XDPH_PWR_BUFFERS, XDPH_PWR_BUFFERS_MIN, 32), 0); + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), 0); + if (size > 0) { + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), 0); + } + if (stride > 0) { + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), 0); + } + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_align, SPA_POD_Int(XDPH_PWR_ALIGN), 0); + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(datatype), 0); + return (spa_pod*)spa_pod_builder_pop(b, &f[0]); +} + +spa_pod* fixate_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifier) { + spa_pod_frame f[1]; + + spa_video_format format_without_alpha = pwStripAlpha(format); + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); + spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + /* format */ + if (modifier || format_without_alpha == SPA_VIDEO_FORMAT_UNKNOWN) { + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + } else { + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, format, format, format_without_alpha), 0); + } + /* modifiers */ + if (modifier) { + // implicit modifier + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_long(b, *modifier); + } + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), 0); + // variable framerate + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(0, 1)), 0); + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&SPA_FRACTION(framerate, 1), &SPA_FRACTION(1, 1), &SPA_FRACTION(framerate, 1)), 0); + return (spa_pod*)spa_pod_builder_pop(b, &f[0]); +} + +spa_pod* build_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifiers, int modifier_count) { + spa_pod_frame f[2]; + int i, c; + + spa_video_format format_without_alpha = pwStripAlpha(format); + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); + spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + /* format */ + if (modifier_count > 0 || format_without_alpha == SPA_VIDEO_FORMAT_UNKNOWN) { + // modifiers are defined only in combinations with their format + // we should not announce the format without alpha + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + } else { + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, format, format, format_without_alpha), 0); + } + /* modifiers */ + if (modifier_count > 0) { + // build an enumeration of modifiers + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); + // modifiers from the array + for (i = 0, c = 0; i < modifier_count; i++) { + spa_pod_builder_long(b, modifiers[i]); + if (c++ == 0) + spa_pod_builder_long(b, modifiers[i]); + } + spa_pod_builder_pop(b, &f[1]); + } + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), 0); + // variable framerate + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(0, 1)), 0); + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&SPA_FRACTION(framerate, 1), &SPA_FRACTION(1, 1), &SPA_FRACTION(framerate, 1)), 0); + return (spa_pod*)spa_pod_builder_pop(b, &f[0]); +} + +void randname(char* buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + assert(buf[i] == 'X'); + buf[i] = 'A' + (r & 15) + (r & 16) * 2; + r >>= 5; + } +} + +int anonymous_shm_open() { + char name[] = "/xdph-shm-XXXXXX"; + int retries = 100; + + do { + randname(name + strlen(name) - 6); + + --retries; + // shm_open guarantees that O_CLOEXEC is set + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +wl_buffer* import_wl_shm_buffer(int fd, wl_shm_format fmt, int width, int height, int stride) { + int size = stride * height; + + if (fd < 0) + return NULL; + + wl_shm_pool* pool = wl_shm_create_pool(g_pPortalManager->m_sWaylandConnection.shm, fd, size); + wl_buffer* buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, fmt); + wl_shm_pool_destroy(pool); + + return buffer; +} diff --git a/src/shared/ScreencopyShared.hpp b/src/shared/ScreencopyShared.hpp new file mode 100644 index 0000000..d626da6 --- /dev/null +++ b/src/shared/ScreencopyShared.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +extern "C" { +#include + +#undef SPA_VERSION_POD_BUILDER_CALLBACKS +#define SPA_VERSION_POD_BUILDER_CALLBACKS .version = 0 +#include +#include +#include +#include +#include +#include +#undef SPA_VERSION_POD_BUILDER_CALLBACKS +#define SPA_VERSION_POD_BUILDER_CALLBACKS 0 +} +#include + +#define XDPH_PWR_BUFFERS 4 +#define XDPH_PWR_BUFFERS_MIN 2 +#define XDPH_PWR_ALIGN 16 + +enum eSelectionType +{ + TYPE_INVALID = -1, + TYPE_OUTPUT = 0, + TYPE_WINDOW, + TYPE_GEOMETRY, + TYPE_WORKSPACE, +}; + +struct zwlr_foreign_toplevel_handle_v1; + +struct SSelectionData { + eSelectionType type = TYPE_INVALID; + std::string output; + zwlr_foreign_toplevel_handle_v1* windowHandle = nullptr; + uint32_t x = 0, y = 0, w = 0, h = 0; // for TYPE_GEOMETRY + bool allowToken = false; +}; + +struct wl_buffer; + +SSelectionData promptForScreencopySelection(); +uint32_t drmFourccFromSHM(wl_shm_format format); +spa_video_format pwFromDrmFourcc(uint32_t format); +wl_shm_format wlSHMFromDrmFourcc(uint32_t format); +spa_video_format pwStripAlpha(spa_video_format format); +std::string getRandName(std::string prefix); +spa_pod* build_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifiers, int modifier_count); +spa_pod* fixate_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifier); +spa_pod* build_buffer(spa_pod_builder* b, uint32_t blocks, uint32_t size, uint32_t stride, uint32_t datatype); +int anonymous_shm_open(); +wl_buffer* import_wl_shm_buffer(int fd, wl_shm_format fmt, int width, int height, int stride); \ No newline at end of file diff --git a/src/shared/Session.cpp b/src/shared/Session.cpp new file mode 100644 index 0000000..2305afb --- /dev/null +++ b/src/shared/Session.cpp @@ -0,0 +1,59 @@ +#include "Session.hpp" +#include "../core/PortalManager.hpp" +#include "../helpers/Log.hpp" + +static void onCloseRequest(sdbus::MethodCall& call, SDBusRequest* req) { + Debug::log(TRACE, "[internal] Close Request {}", (void*)req); + + if (!req) + return; + + auto r = call.createReply(); + r.send(); + + req->onDestroy(); + req->object.release(); +} + +static void onCloseSession(sdbus::MethodCall& call, SDBusSession* sess) { + Debug::log(TRACE, "[internal] Close Session {}", (void*)sess); + + if (!sess) + return; + + auto r = call.createReply(); + r.send(); + + sess->onDestroy(); + sess->object.release(); +} + +std::unique_ptr createDBusSession(sdbus::ObjectPath handle) { + Debug::log(TRACE, "[internal] Create Session {}", handle.c_str()); + + std::unique_ptr pSession = std::make_unique(); + const auto PSESSION = pSession.get(); + + pSession->object = sdbus::createObject(*g_pPortalManager->getConnection(), handle); + + pSession->object->registerMethod("org.freedesktop.impl.portal.Session", "Close", "", "", [PSESSION](sdbus::MethodCall c) { onCloseSession(c, PSESSION); }); + + pSession->object->finishRegistration(); + + return pSession; +} + +std::unique_ptr createDBusRequest(sdbus::ObjectPath handle) { + Debug::log(TRACE, "[internal] Create Request {}", handle.c_str()); + + std::unique_ptr pRequest = std::make_unique(); + const auto PREQUEST = pRequest.get(); + + pRequest->object = sdbus::createObject(*g_pPortalManager->getConnection(), handle); + + pRequest->object->registerMethod("org.freedesktop.impl.portal.Request", "Close", "", "", [PREQUEST](sdbus::MethodCall c) { onCloseRequest(c, PREQUEST); }); + + pRequest->object->finishRegistration(); + + return pRequest; +} \ No newline at end of file diff --git a/src/shared/Session.hpp b/src/shared/Session.hpp new file mode 100644 index 0000000..a006bc9 --- /dev/null +++ b/src/shared/Session.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +struct SDBusSession { + std::unique_ptr object; + sdbus::ObjectPath handle; + std::function onDestroy; +}; + +struct SDBusRequest { + std::unique_ptr object; + sdbus::ObjectPath handle; + std::function onDestroy; +}; + +std::unique_ptr createDBusSession(sdbus::ObjectPath handle); +std::unique_ptr createDBusRequest(sdbus::ObjectPath handle); \ No newline at end of file diff --git a/src/shared/ToplevelManager.cpp b/src/shared/ToplevelManager.cpp new file mode 100644 index 0000000..86cfe01 --- /dev/null +++ b/src/shared/ToplevelManager.cpp @@ -0,0 +1,96 @@ +#include "ToplevelManager.hpp" +#include "../helpers/Log.hpp" + +static void toplevelTitle(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, const char* title) { + const auto PTL = (SToplevelHandle*)data; + + if (title) + PTL->windowTitle = title; + + Debug::log(TRACE, "[toplevel] toplevel at {} set title to {}", data, PTL->windowTitle); +} + +static void toplevelAppid(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, const char* app_id) { + const auto PTL = (SToplevelHandle*)data; + + if (app_id) + PTL->windowClass = app_id; + + Debug::log(TRACE, "[toplevel] toplevel at {} set class to {}", data, PTL->windowClass); +} + +static void toplevelEnterOutput(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, wl_output* output) { + ; +} + +static void toplevelLeaveOutput(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, wl_output* output) { + ; +} + +static void toplevelState(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, wl_array* state) { + ; +} + +static void toplevelDone(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1) { + ; +} + +static void toplevelClosed(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1) { + const auto PTL = (SToplevelHandle*)data; + + std::erase_if(PTL->mgr->m_vToplevels, [&](const auto& e) { return e.get() == PTL; }); + + Debug::log(TRACE, "[toplevel] toplevel at {} closed", data); +} + +static void toplevelParent(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, struct zwlr_foreign_toplevel_handle_v1* parent) { + ; +} + +inline const zwlr_foreign_toplevel_handle_v1_listener toplevelListener = { + .title = toplevelTitle, + .app_id = toplevelAppid, + .output_enter = toplevelEnterOutput, + .output_leave = toplevelLeaveOutput, + .state = toplevelState, + .done = toplevelDone, + .closed = toplevelClosed, + .parent = toplevelParent, +}; + +static void managerToplevel(void* data, zwlr_foreign_toplevel_manager_v1* mgr, zwlr_foreign_toplevel_handle_v1* toplevel) { + const auto PMGR = (CToplevelManager*)data; + + Debug::log(TRACE, "[toplevel] New toplevel at {}", (void*)toplevel); + + const auto PTL = PMGR->m_vToplevels.emplace_back(std::make_unique("?", "?", toplevel, PMGR)).get(); + + zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &toplevelListener, PTL); +} + +static void managerFinished(void* data, zwlr_foreign_toplevel_manager_v1* mgr) { + const auto PMGR = (CToplevelManager*)data; + + Debug::log(ERR, "[toplevel] Compositor sent .finished???"); + + PMGR->m_vToplevels.clear(); +} + +inline const zwlr_foreign_toplevel_manager_v1_listener managerListener = { + .toplevel = managerToplevel, + .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) + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/shared/ToplevelManager.hpp b/src/shared/ToplevelManager.hpp new file mode 100644 index 0000000..bbf5049 --- /dev/null +++ b/src/shared/ToplevelManager.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include + +class CToplevelManager; + +struct SToplevelHandle { + std::string windowClass; + std::string windowTitle; + zwlr_foreign_toplevel_handle_v1* handle = nullptr; + CToplevelManager* mgr = nullptr; +}; + +class CToplevelManager { + public: + CToplevelManager(zwlr_foreign_toplevel_manager_v1* mgr); + + bool exists(zwlr_foreign_toplevel_handle_v1* handle); + + std::vector> m_vToplevels; + + private: + zwlr_foreign_toplevel_manager_v1* m_pManager; +}; \ No newline at end of file diff --git a/subprojects/sdbus-cpp b/subprojects/sdbus-cpp new file mode 160000 index 0000000..0eda855 --- /dev/null +++ b/subprojects/sdbus-cpp @@ -0,0 +1 @@ +Subproject commit 0eda85574546d19d9f06d6d5418bc192b3846f96