mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-12-22 17:59:47 +01:00
Woo! Full rewrite, v1.0 release. (#78)
This commit is contained in:
parent
57a3a41ba6
commit
022469529c
68 changed files with 3331 additions and 5419 deletions
65
.clang-format
Normal file
65
.clang-format
Normal 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
6
.gitignore
vendored
|
@ -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
3
.gitmodules
vendored
|
@ -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
77
CMakeLists.txt
Normal 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
42
LICENSE
|
@ -1,21 +1,29 @@
|
|||
MIT License
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2018 emersion
|
||||
Copyright (c) 2023, vaxerski
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
21
Makefile
Normal file
21
Makefile
Normal 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/
|
46
README.md
46
README.md
|
@ -1,52 +1,16 @@
|
|||
# xdg-desktop-portal-hyprland
|
||||
|
||||
[xdg-desktop-portal] backend for hyprland
|
||||
|
||||
## What and why?
|
||||
Due to reasons explained in [hyprland-protocols](https://github.com/hyprwm/hyprland-protocols),
|
||||
we have a separate desktop portal impl for Hyprland.
|
||||
|
||||
Although `-wlr` **does** work with Hyprland, `-hyprland` offers more features.
|
||||
|
||||
## Additional dependencies
|
||||
XDPH depends on `qt6` and `qt6-wayland` for the sharing selector. Lack of either will
|
||||
cause screensharing to not work at all.
|
||||
An [XDG Desktop Portal](https://github.com/flatpak/xdg-desktop-portal) backend for Hyprland.
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
meson build --prefix=/usr
|
||||
ninja -C build
|
||||
cd hyprland-share-picker && make all && cd ..
|
||||
make all
|
||||
```
|
||||
|
||||
## Installing
|
||||
|
||||
### From Source
|
||||
|
||||
```sh
|
||||
ninja -C build install
|
||||
sudo cp ./hyprland-share-picker/build/hyprland-share-picker /usr/bin
|
||||
sudo make install
|
||||
```
|
||||
|
||||
### AUR
|
||||
```sh
|
||||
yay -S xdg-desktop-portal-hyprland-git
|
||||
```
|
||||
## Running, FAQs, etc.
|
||||
See [the Hyprland wiki](https://wiki.hyprland.org/Useful-Utilities/Hyprland-desktop-portal/)
|
||||
|
||||
## Usage
|
||||
|
||||
Although should start automatically, consult [the Hyprland wiki](https://wiki.hyprland.org/Useful-Utilities/Hyprland-desktop-portal/)
|
||||
in case of issues.
|
||||
|
||||
## For other wlroots-based compositors
|
||||
If you are a developer and wish to support features that XDPH provides, make sure to support those protocols:
|
||||
- `wlr_foreign_toplevel_management_unstable_v1`
|
||||
- `hyprland_toplevel_export_v1` - XDPH uses Rev2 exclusively (`_with_toplevel_handle`)
|
||||
|
||||
|
||||
## License
|
||||
MIT
|
||||
|
||||
[xdg-desktop-portal]: https://github.com/flatpak/xdg-desktop-portal
|
||||
[screencast compatibility]: https://github.com/emersion/xdg-desktop-portal-wlr/wiki/Screencast-Compatibility
|
||||
|
|
1
VERSION
Normal file
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
|||
1.0.0
|
|
@ -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": {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 */
|
|
@ -1,10 +0,0 @@
|
|||
#ifndef SCREENSHOT_H
|
||||
#define SCREENSHOT_H
|
||||
|
||||
|
||||
struct xdpw_ppm_pixel {
|
||||
int max_color_value;
|
||||
unsigned char red, green, blue;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,6 +0,0 @@
|
|||
#ifndef SCREENSHOT_COMMON_H
|
||||
#define SCREENSHOT_COMMON_H
|
||||
|
||||
#define XDP_SHOT_PROTO_VER 2
|
||||
|
||||
#endif
|
|
@ -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
|
|
@ -1,6 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
char *getFormat(const char *fmt, ...);
|
|
@ -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
|
|
@ -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
|
139
meson.build
139
meson.build
|
@ -1,111 +1,38 @@
|
|||
project(
|
||||
'xdg-desktop-portal-hyprland',
|
||||
'c',
|
||||
version: '0.5.0',
|
||||
license: 'MIT',
|
||||
meson_version: '>=0.58.0',
|
||||
default_options: ['c_std=c11', 'warning_level=2', 'werror=false'],
|
||||
)
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
add_project_arguments(cc.get_supported_arguments([
|
||||
'-Wno-missing-braces',
|
||||
'-Wno-missing-field-initializers',
|
||||
'-Wno-unused-parameter',
|
||||
'-D_POSIX_C_SOURCE=200809L',
|
||||
'-D_GNU_SOURCE',
|
||||
]), language: 'c')
|
||||
|
||||
prefix = get_option('prefix')
|
||||
sysconfdir = get_option('sysconfdir')
|
||||
add_project_arguments('-DSYSCONFDIR="@0@"'.format(join_paths(prefix, sysconfdir)), language : 'c')
|
||||
|
||||
inc = include_directories('include')
|
||||
|
||||
rt = cc.find_library('rt')
|
||||
pipewire = dependency('libpipewire-0.3', version: '>= 0.3.62')
|
||||
hyprland_protos = dependency('hyprland-protocols', version: '>=0.2', fallback: 'hyprland-protocols')
|
||||
wayland_client = dependency('wayland-client')
|
||||
wayland_protos = dependency('wayland-protocols', version: '>=1.24')
|
||||
iniparser = dependency('inih')
|
||||
gbm = dependency('gbm')
|
||||
drm = dependency('libdrm')
|
||||
uuid = dependency('uuid')
|
||||
|
||||
epoll = dependency('', required: false)
|
||||
if (not cc.has_function('timerfd_create', prefix: '#include <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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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=";
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
4
org.freedesktop.impl.portal.desktop.hyprland.service
Normal file
4
org.freedesktop.impl.portal.desktop.hyprland.service
Normal 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
|
|
@ -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
384
src/core/PortalManager.cpp
Normal 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, ®istryListener, nullptr);
|
||||
|
||||
pw_init(nullptr, nullptr);
|
||||
m_sPipewire.loop = pw_loop_new(nullptr);
|
||||
|
||||
if (!m_sPipewire.loop)
|
||||
Debug::log(ERR, "Pipewire: refused to create a loop. Screensharing will not work.");
|
||||
|
||||
Debug::log(LOG, "Gathering exported interfaces");
|
||||
|
||||
wl_display_roundtrip(m_sWaylandConnection.display);
|
||||
|
||||
if (!m_sPortals.screencopy)
|
||||
Debug::log(WARN, "Screencopy not started: compositor doesn't support zwlr_screencopy_v1 or pw refused a loop");
|
||||
else if (m_sWaylandConnection.hyprlandToplevelMgr)
|
||||
m_sPortals.screencopy->appendToplevelExport(m_sWaylandConnection.hyprlandToplevelMgr);
|
||||
|
||||
wl_display_roundtrip(m_sWaylandConnection.display);
|
||||
|
||||
while (1) {
|
||||
// dbus events
|
||||
m_mEventLock.lock();
|
||||
|
||||
while (m_pConnection->processPendingRequest()) {
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<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);
|
||||
}
|
81
src/core/PortalManager.hpp
Normal file
81
src/core/PortalManager.hpp
Normal 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;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
291
src/core/main.c
291
src/core/main.c
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
1
src/helpers/Log.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
#include "Log.hpp"
|
55
src/helpers/Log.hpp
Normal file
55
src/helpers/Log.hpp
Normal 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";
|
||||
}
|
||||
};
|
17
src/helpers/MiscFunctions.cpp
Normal file
17
src/helpers/MiscFunctions.cpp
Normal 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;
|
||||
}
|
4
src/helpers/MiscFunctions.hpp
Normal file
4
src/helpers/MiscFunctions.hpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
std::string execAndGet(const char* cmd);
|
11
src/helpers/Timer.cpp
Normal file
11
src/helpers/Timer.cpp
Normal 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
17
src/helpers/Timer.hpp
Normal 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
41
src/main.cpp
Normal 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
21
src/meson.build
Normal 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')
|
||||
)
|
237
src/portals/GlobalShortcuts.cpp
Normal file
237
src/portals/GlobalShortcuts.cpp
Normal 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);
|
||||
}
|
48
src/portals/GlobalShortcuts.hpp
Normal file
48
src/portals/GlobalShortcuts.hpp
Normal 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
1310
src/portals/Screencopy.cpp
Normal file
File diff suppressed because it is too large
Load diff
165
src/portals/Screencopy.hpp
Normal file
165
src/portals/Screencopy.hpp
Normal 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;
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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, ¶ms[0]);
|
||||
pw_stream_update_params(stream, params, n_params);
|
||||
spa_pod_dynamic_builder_clean(&b[0]);
|
||||
spa_pod_dynamic_builder_clean(&b[1]);
|
||||
spa_pod_dynamic_builder_clean(&b[2]);
|
||||
return;
|
||||
|
||||
fixate_format:
|
||||
params[0] = fixate_format(&b[2].b, xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[cast->buffer_type].format),
|
||||
cast->screencopy_frame_info[cast->buffer_type].width, cast->screencopy_frame_info[cast->buffer_type].height, cast->framerate, &modifier);
|
||||
|
||||
n_params = build_formats(builder, cast, ¶ms[1]);
|
||||
n_params++;
|
||||
|
||||
pw_stream_update_params(stream, params, n_params);
|
||||
spa_pod_dynamic_builder_clean(&b[0]);
|
||||
spa_pod_dynamic_builder_clean(&b[1]);
|
||||
spa_pod_dynamic_builder_clean(&b[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cast->pwr_format.modifier == DRM_FORMAT_MOD_INVALID) {
|
||||
blocks = 1;
|
||||
} else {
|
||||
blocks = gbm_device_get_format_modifier_plane_count(cast->ctx->gbm,
|
||||
cast->screencopy_frame_info[DMABUF].format, cast->pwr_format.modifier);
|
||||
}
|
||||
} else {
|
||||
cast->buffer_type = WL_SHM;
|
||||
blocks = 1;
|
||||
data_type = 1<<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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
}
|
315
src/shared/ScreencopyShared.cpp
Normal file
315
src/shared/ScreencopyShared.cpp
Normal 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;
|
||||
}
|
56
src/shared/ScreencopyShared.hpp
Normal file
56
src/shared/ScreencopyShared.hpp
Normal 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
59
src/shared/Session.cpp
Normal 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
18
src/shared/Session.hpp
Normal 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);
|
96
src/shared/ToplevelManager.cpp
Normal file
96
src/shared/ToplevelManager.cpp
Normal 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;
|
||||
}
|
28
src/shared/ToplevelManager.hpp
Normal file
28
src/shared/ToplevelManager.hpp
Normal 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
1
subprojects/sdbus-cpp
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 0eda85574546d19d9f06d6d5418bc192b3846f96
|
Loading…
Reference in a new issue