Woo! Full rewrite, v1.0 release. (#78)

This commit is contained in:
Vaxry 2023-09-06 20:36:48 +02:00 committed by GitHub
parent 57a3a41ba6
commit 022469529c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 3331 additions and 5419 deletions

65
.clang-format Normal file
View File

@ -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

6
.gitignore vendored
View File

@ -55,6 +55,10 @@ dkms.conf
build/
build-*/
.cache
.vscode/
hyprland-share-picker/build/
hyprland-share-picker/build/
protocols/*.c
protocols/*.h

3
.gitmodules vendored
View File

@ -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

77
CMakeLists.txt Normal file
View File

@ -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)
##

42
LICENSE
View File

@ -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.

21
Makefile Normal file
View File

@ -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/

View File

@ -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

1
VERSION Normal file
View File

@ -0,0 +1 @@
1.0.0

View File

@ -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": {

View File

@ -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 {

View File

@ -18,8 +18,8 @@
#include "mainpicker.h"
std::string execAndGet(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, decltype(&pclose)> 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<SWindowEntry> 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<SWindowEntry> 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<QWidget*>("screens")->findChild<QScrollArea*>("scrollArea")->findChild<QWidget*>("scrollAreaWidgetContents");
const auto SCREENS_SCROLL_AREA_CONTENTS =
(QWidget*)TAB1->findChild<QWidget*>("screens")->findChild<QScrollArea*>("scrollArea")->findChild<QWidget*>("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<QWidget*>("windows")->findChild<QScrollArea*>("scrollArea_2")->findChild<QWidget*>("scrollAreaWidgetContents_2");
const auto WINDOWS_SCROLL_AREA_CONTENTS =
(QWidget*)TAB1->findChild<QWidget*>("windows")->findChild<QScrollArea*>("scrollArea_2")->findChild<QWidget*>("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<QWidget*>("region");
const auto REGION_OBJECT = (QWidget*)TAB1->findChild<QWidget*>("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;

View File

@ -66,7 +66,7 @@
<enum>QTabWidget::North</enum>
</property>
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="screens">
<attribute name="title">
@ -78,9 +78,12 @@
<x>9</x>
<y>9</y>
<width>461</width>
<height>241</height>
<height>201</height>
</rect>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>false</bool>
</property>
@ -115,9 +118,12 @@
<x>9</x>
<y>9</y>
<width>461</width>
<height>241</height>
<height>201</height>
</rect>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>false</bool>
</property>
@ -148,6 +154,22 @@
</attribute>
</widget>
</widget>
<widget class="QCheckBox" name="checkBox">
<property name="geometry">
<rect>
<x>340</x>
<y>256</y>
<width>140</width>
<height>21</height>
</rect>
</property>
<property name="toolTip">
<string>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.</string>
</property>
<property name="text">
<string>Allow a restore token</string>
</property>
</widget>
</widget>
</widget>
<tabstops>

View File

@ -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

View File

@ -1,18 +0,0 @@
#ifndef FPS_LIMIT_H
#define FPS_LIMIT_H
#include <stdint.h>
#include <time.h>
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

View File

@ -1,31 +0,0 @@
#pragma once
#include <stdbool.h>
#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);

View File

@ -1,19 +0,0 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <stdio.h>
#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

View File

@ -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

View File

@ -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

View File

@ -1,238 +0,0 @@
#ifndef SCREENCAST_COMMON_H
#define SCREENCAST_COMMON_H
#include <gbm.h>
#include <pipewire/pipewire.h>
#include <spa/param/video/format-utils.h>
#include <stdbool.h>
#include <wayland-client-protocol.h>
#include <xf86drm.h>
#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 */

View File

@ -1,10 +0,0 @@
#ifndef SCREENSHOT_H
#define SCREENSHOT_H
struct xdpw_ppm_pixel {
int max_color_value;
unsigned char red, green, blue;
};
#endif

View File

@ -1,6 +0,0 @@
#ifndef SCREENSHOT_COMMON_H
#define SCREENSHOT_COMMON_H
#define XDP_SHOT_PROTO_VER 2
#endif

View File

@ -1,18 +0,0 @@
#ifndef TIMESPEC_UTIL_H
#define TIMESPEC_UTIL_H
#include <time.h>
#include <stdbool.h>
#include <stdint.h>
#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

View File

@ -1,6 +0,0 @@
#pragma once
#include <stdarg.h>
#include <stddef.h>
char *getFormat(const char *fmt, ...);

View File

@ -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

View File

@ -1,78 +0,0 @@
#ifndef XDPW_H
#define XDPW_H
#include <wayland-client.h>
#ifdef HAVE_LIBSYSTEMD
#include <systemd/sd-bus.h>
#elif HAVE_LIBELOGIND
#include <elogind/sd-bus.h>
#elif HAVE_BASU
#include <basu/sd-bus.h>
#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

View File

@ -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 <sys/timerfd.h>') or
not cc.has_function('signalfd', prefix: '#include <sys/signalfd.h>'))
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')

View File

@ -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')

View File

@ -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;
};
}

View File

@ -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;

View File

@ -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=";
};
});
};
}

View File

@ -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

View File

@ -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@'],
)

384
src/core/PortalManager.cpp Normal file
View File

@ -0,0 +1,384 @@
#include "PortalManager.hpp"
#include "../helpers/Log.hpp"
#include <protocols/hyprland-global-shortcuts-v1-protocol.h>
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
#include <protocols/wlr-foreign-toplevel-management-unstable-v1-protocol.h>
#include <protocols/wlr-screencopy-unstable-v1-protocol.h>
#include <protocols/linux-dmabuf-unstable-v1-protocol.h>
#include <pipewire/pipewire.h>
#include <sys/poll.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <thread>
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<CScreencopyPortal>((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<CGlobalShortcutsPortal>(
(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<SOutput>()).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<CToplevelManager>((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, &registryListener, 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<CTimer*> 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);
}

View File

@ -0,0 +1,81 @@
#pragma once
#include <memory>
#include <sdbus-c++/sdbus-c++.h>
#include <wayland-client.h>
#include "../portals/Screencopy.hpp"
#include "../portals/GlobalShortcuts.hpp"
#include "../helpers/Timer.hpp"
#include "../shared/ToplevelManager.hpp"
#include <gbm.h>
#include <xf86drm.h>
#include <mutex>
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<CScreencopyPortal> screencopy;
std::unique_ptr<CGlobalShortcutsPortal> globalShortcuts;
} m_sPortals;
struct {
std::unique_ptr<CToplevelManager> 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<SDMABUFModifier> m_vDMABUFMods;
std::vector<std::unique_ptr<CTimer>> m_vTimers;
gbm_device* createGBMDevice(drmDevice* dev);
private:
std::unique_ptr<sdbus::IConnection> m_pConnection;
std::vector<std::unique_ptr<SOutput>> m_vOutputs;
std::mutex m_mEventLock;
};
inline std::unique_ptr<CPortalManager> g_pPortalManager;

View File

@ -1,194 +0,0 @@
#include "config.h"
#include "xdpw.h"
#include "logger.h"
#include "screencast_common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ini.h>
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);
}
}

View File

@ -1,85 +0,0 @@
#include "logger.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
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);
}

View File

@ -1,291 +0,0 @@
#include <errno.h>
#include <getopt.h>
#include <pipewire/pipewire.h>
#include <poll.h>
#include <spa/utils/result.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/timerfd.h>
#include <unistd.h>
#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=<loglevel> Select log level (default is ERROR).\n"
" QUIET, ERROR, WARN, INFO, DEBUG, TRACE\n"
" -c, --config=<config file> 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;
}

View File

@ -1,60 +0,0 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

View File

@ -1,88 +0,0 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#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);
}

View File

@ -1,69 +0,0 @@
#include <poll.h>
#include <wayland-util.h>
#include <sys/timerfd.h>
#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);
}

View File

@ -1,33 +0,0 @@
#include "timespec_util.h"
#include <time.h>
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;
}

View File

@ -1,13 +0,0 @@
#include <stdio.h>
#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;
}

View File

@ -1,537 +0,0 @@
#include "include/global_shortcuts.h"
#include <string.h>
#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
}

1
src/helpers/Log.cpp Normal file
View File

@ -0,0 +1 @@
#include "Log.hpp"

55
src/helpers/Log.hpp Normal file
View File

@ -0,0 +1,55 @@
#pragma once
#include <format>
#include <iostream>
#include <string>
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 <typename... Args>
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";
}
};

View File

@ -0,0 +1,17 @@
#include "MiscFunctions.hpp"
#include <memory>
#include "../helpers/Log.hpp"
std::string execAndGet(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
const std::unique_ptr<FILE, decltype(&pclose)> 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;
}

View File

@ -0,0 +1,4 @@
#pragma once
#include <string>
std::string execAndGet(const char* cmd);

11
src/helpers/Timer.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "Timer.hpp"
CTimer::CTimer(float ms, std::function<void()> 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));
}

17
src/helpers/Timer.hpp Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <functional>
#include <chrono>
class CTimer {
public:
CTimer(float ms, std::function<void()> callback);
bool passed() const;
std::function<void()> m_fnCallback;
private:
std::chrono::high_resolution_clock::time_point m_tStart;
float m_fDuration;
};

41
src/main.cpp Normal file
View File

@ -0,0 +1,41 @@
#include <sdbus-c++/sdbus-c++.h>
#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<CPortalManager>();
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;
}

21
src/meson.build Normal file
View File

@ -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')
)

View File

@ -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<SSession>(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<std::string, sdbus::Variant> opts;
call >> opts;
for (auto& [k, v] : opts) {
if (k == "shortcuts") {
PSESSION->registered = true;
std::vector<sdbus::Struct<std::string, std::unordered_map<std::string, sdbus::Variant>>> shortcuts;
shortcuts = v.get<std::vector<sdbus::Struct<std::string, std::unordered_map<std::string, sdbus::Variant>>>>();
for (auto& s : shortcuts) {
const auto PSHORTCUT = PSESSION->keybinds.emplace_back(std::make_unique<SKeybind>()).get();
PSHORTCUT->id = s.get<0>();
std::unordered_map<std::string, sdbus::Variant> data = s.get<1>();
for (auto& [k, v] : data) {
if (k == "description") {
PSHORTCUT->description = v.get<std::string>();
} 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<std::string, sdbus::Variant>{};
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<sdbus::Struct<std::string, std::unordered_map<std::string, sdbus::Variant>>> shortcuts;
std::vector<sdbus::Struct<std::string, std::unordered_map<std::string, sdbus::Variant>>> 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<SKeybind>()).get();
PSHORTCUT->id = s.get<0>();
std::unordered_map<std::string, sdbus::Variant> data = s.get<1>();
for (auto& [k, v] : data) {
if (k == "description") {
PSHORTCUT->description = v.get<std::string>();
} 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<std::string, sdbus::Variant> 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<std::string, sdbus::Variant> 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<sdbus::Struct<std::string, std::unordered_map<std::string, sdbus::Variant>>> shortcuts;
for (auto& s : PSESSION->keybinds) {
std::unordered_map<std::string, sdbus::Variant> opts;
opts["description"] = s->description;
opts["trigger_description"] = "";
shortcuts.push_back({s->id, opts});
}
auto reply = call.createReply();
std::unordered_map<std::string, sdbus::Variant> 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<std::string, sdbus::Variant>{};
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<std::string, sdbus::Variant>{};
m_pObject->emitSignal(signal);
}

View File

@ -0,0 +1,48 @@
#pragma once
#include <sdbus-c++/sdbus-c++.h>
#include <protocols/hyprland-global-shortcuts-v1-protocol.h>
#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<SDBusRequest> request;
std::unique_ptr<SDBusSession> session;
bool registered = false;
std::vector<std::unique_ptr<SKeybind>> keybinds;
};
std::vector<std::unique_ptr<SSession>> m_vSessions;
private:
struct {
hyprland_global_shortcuts_manager_v1* manager;
} m_sState;
std::unique_ptr<sdbus::IObject> 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";
};

1310
src/portals/Screencopy.cpp Normal file

File diff suppressed because it is too large Load Diff

165
src/portals/Screencopy.hpp Normal file
View File

@ -0,0 +1,165 @@
#pragma once
#include <protocols/wlr-screencopy-unstable-v1-protocol.h>
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
#include <sdbus-c++/sdbus-c++.h>
#include "../shared/ScreencopyShared.hpp"
#include <gbm.h>
#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<SDBusRequest> request;
std::unique_ptr<SDBusSession> 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<CPipewireConnection> m_pPipewire;
private:
std::unique_ptr<sdbus::IObject> m_pObject;
std::vector<std::unique_ptr<SSession>> 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<std::unique_ptr<SBuffer>> buffers;
};
std::unique_ptr<SBuffer> 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<std::unique_ptr<SPWStream>> 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;
};

View File

@ -1,70 +0,0 @@
#include "fps_limit.h"
#include "logger.h"
#include "timespec_util.h"
#include <stdint.h>
#include <time.h>
#include <unistd.h>
#include <assert.h>
#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;
}

View File

@ -1,634 +0,0 @@
#include "pipewire_screencast.h"
#include <assert.h>
#include <libdrm/drm_fourcc.h>
#include <pipewire/pipewire.h>
#include <spa/buffer/meta.h>
#include <spa/param/format-utils.h>
#include <spa/param/props.h>
#include <spa/param/video/format-utils.h>
#include <spa/pod/dynamic.h>
#include <spa/utils/result.h>
#include <sys/mman.h>
#include <unistd.h>
#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<<SPA_DATA_DmaBuf;
assert(cast->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, &params[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, &params[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<<SPA_DATA_MemFd;
}
logprint(DEBUG, "pipewire: Format negotiated:");
logprint(DEBUG, "pipewire: buffer_type: %u (%u)", cast->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;
}
}

View File

@ -1,731 +0,0 @@
#include "screencast.h"
#include <assert.h>
#include <drm_fourcc.h>
#include <errno.h>
#include <spa/utils/result.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <uuid/uuid.h>
#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;
}

View File

@ -1,426 +0,0 @@
#include "xdpw.h"
#include "screencast_common.h"
#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libdrm/drm_fourcc.h>
#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;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,300 +0,0 @@
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#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);
}

View File

@ -0,0 +1,315 @@
#include "ScreencopyShared.hpp"
#include "../helpers/MiscFunctions.hpp"
#include <wayland-client.h>
#include "../helpers/Log.hpp"
#include <libdrm/drm_fourcc.h>
#include <assert.h>
#include "../core/PortalManager.hpp"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
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;
}

View File

@ -0,0 +1,56 @@
#pragma once
#include <string>
#include <cstdint>
extern "C" {
#include <spa/pod/builder.h>
#undef SPA_VERSION_POD_BUILDER_CALLBACKS
#define SPA_VERSION_POD_BUILDER_CALLBACKS .version = 0
#include <spa/buffer/meta.h>
#include <spa/utils/result.h>
#include <spa/param/props.h>
#include <spa/param/format-utils.h>
#include <spa/param/video/format-utils.h>
#include <spa/pod/dynamic.h>
#undef SPA_VERSION_POD_BUILDER_CALLBACKS
#define SPA_VERSION_POD_BUILDER_CALLBACKS 0
}
#include <wayland-client.h>
#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);

59
src/shared/Session.cpp Normal file
View File

@ -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<SDBusSession> createDBusSession(sdbus::ObjectPath handle) {
Debug::log(TRACE, "[internal] Create Session {}", handle.c_str());
std::unique_ptr<SDBusSession> pSession = std::make_unique<SDBusSession>();
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<SDBusRequest> createDBusRequest(sdbus::ObjectPath handle) {
Debug::log(TRACE, "[internal] Create Request {}", handle.c_str());
std::unique_ptr<SDBusRequest> pRequest = std::make_unique<SDBusRequest>();
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;
}

18
src/shared/Session.hpp Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <sdbus-c++/sdbus-c++.h>
struct SDBusSession {
std::unique_ptr<sdbus::IObject> object;
sdbus::ObjectPath handle;
std::function<void()> onDestroy;
};
struct SDBusRequest {
std::unique_ptr<sdbus::IObject> object;
sdbus::ObjectPath handle;
std::function<void()> onDestroy;
};
std::unique_ptr<SDBusSession> createDBusSession(sdbus::ObjectPath handle);
std::unique_ptr<SDBusRequest> createDBusRequest(sdbus::ObjectPath handle);

View File

@ -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<SToplevelHandle>("?", "?", 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;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <wayland-client.h>
#include <protocols/wlr-foreign-toplevel-management-unstable-v1-protocol.h>
#include <string>
#include <vector>
#include <memory>
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<std::unique_ptr<SToplevelHandle>> m_vToplevels;
private:
zwlr_foreign_toplevel_manager_v1* m_pManager;
};

1
subprojects/sdbus-cpp Submodule

@ -0,0 +1 @@
Subproject commit 0eda85574546d19d9f06d6d5418bc192b3846f96