mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-11-24 07:05:58 +01:00
Implement hyprland-share-picker
This commit is contained in:
parent
f2e64940ae
commit
80775707ea
17 changed files with 510 additions and 336 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -54,3 +54,7 @@ dkms.conf
|
||||||
# build folder
|
# build folder
|
||||||
build/
|
build/
|
||||||
build-*/
|
build-*/
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
hyprland-share-picker/build/
|
|
@ -13,6 +13,7 @@ Although `-wlr` **does** work with Hyprland, `-hyprland` offers more features.
|
||||||
```sh
|
```sh
|
||||||
meson build
|
meson build
|
||||||
ninja -C build
|
ninja -C build
|
||||||
|
cd hyprland-share-picker && make all && cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
@ -21,6 +22,7 @@ ninja -C build
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ninja -C build install
|
ninja -C build install
|
||||||
|
sudo cp ./hyprland-share-picker/build/hyprland-share-picker /usr/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
11
contrib/systemd/xdg-desktop-portal-hyprland.service.in
Normal file
11
contrib/systemd/xdg-desktop-portal-hyprland.service.in
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Portal service (Hyprland implementation)
|
||||||
|
PartOf=graphical-session.target
|
||||||
|
After=graphical-session.target
|
||||||
|
ConditionEnvironment=WAYLAND_DISPLAY
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.freedesktop.impl.portal.desktop.hyprland
|
||||||
|
ExecStart=@libexecdir@/xdg-desktop-portal-hyprland
|
||||||
|
Restart=on-failure
|
|
@ -1,11 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=Portal service (wlroots implementation)
|
|
||||||
PartOf=graphical-session.target
|
|
||||||
After=graphical-session.target
|
|
||||||
ConditionEnvironment=WAYLAND_DISPLAY
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=dbus
|
|
||||||
BusName=org.freedesktop.impl.portal.desktop.wlr
|
|
||||||
ExecStart=@libexecdir@/xdg-desktop-portal-wlr
|
|
||||||
Restart=on-failure
|
|
61
hyprland-share-picker/CMakeLists.txt
Normal file
61
hyprland-share-picker/CMakeLists.txt
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
project(hyprland-share-picker VERSION 0.1 LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
|
||||||
|
|
||||||
|
set(PROJECT_SOURCES
|
||||||
|
main.cpp
|
||||||
|
mainpicker.cpp
|
||||||
|
mainpicker.h
|
||||||
|
mainpicker.ui
|
||||||
|
)
|
||||||
|
|
||||||
|
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||||
|
qt_add_executable(hyprland-share-picker
|
||||||
|
MANUAL_FINALIZATION
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
)
|
||||||
|
# Define target properties for Android with Qt 6 as:
|
||||||
|
# set_property(TARGET hyprland-share-picker APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||||
|
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||||
|
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
||||||
|
else()
|
||||||
|
if(ANDROID)
|
||||||
|
add_library(hyprland-share-picker SHARED
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
)
|
||||||
|
# Define properties for Android with Qt 5 after find_package() calls as:
|
||||||
|
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||||
|
else()
|
||||||
|
add_executable(hyprland-share-picker
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(hyprland-share-picker PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
||||||
|
|
||||||
|
set_target_properties(hyprland-share-picker PROPERTIES
|
||||||
|
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
|
||||||
|
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||||
|
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||||
|
MACOSX_BUNDLE TRUE
|
||||||
|
WIN32_EXECUTABLE TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS hyprland-share-picker
|
||||||
|
BUNDLE DESTINATION .
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|
||||||
|
if(QT_VERSION_MAJOR EQUAL 6)
|
||||||
|
qt_finalize_executable(hyprland-share-picker)
|
||||||
|
endif()
|
4
hyprland-share-picker/Makefile
Normal file
4
hyprland-share-picker/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
all:
|
||||||
|
mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja
|
||||||
|
cmake --build ./build --config Release --target all -j$(shell nproc)
|
176
hyprland-share-picker/main.cpp
Normal file
176
hyprland-share-picker/main.cpp
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
#include "mainpicker.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QTabWidget>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QtWidgets>
|
||||||
|
#include <QtDebug>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QEvent>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
std::string execAndGet(const char* cmd) {
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
|
||||||
|
result += buffer.data();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QApplication* pickerPtr = nullptr;
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
qputenv("QT_LOGGING_RULES", "qml=false");
|
||||||
|
QApplication picker(argc, argv);
|
||||||
|
pickerPtr = &picker;
|
||||||
|
MainPicker w;
|
||||||
|
|
||||||
|
// get the tabwidget
|
||||||
|
const auto TABWIDGET = (QTabWidget*)w.children()[1]->children()[0];
|
||||||
|
|
||||||
|
const auto TAB1 = (QWidget*)TABWIDGET->children()[0];
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
constexpr int BUTTON_WIDTH = 441;
|
||||||
|
constexpr int BUTTON_HEIGHT = 41;
|
||||||
|
constexpr int BUTTON_PAD = 4;
|
||||||
|
|
||||||
|
for (int i = 0; i < SCREENS.size(); ++i) {
|
||||||
|
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() + ")");
|
||||||
|
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(')'));
|
||||||
|
|
||||||
|
std::cout << "screen:" << ID << "\n";
|
||||||
|
pickerPtr->quit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
// get all windows from hyprctl
|
||||||
|
std::string windowsList = execAndGet("hyprctl clients");
|
||||||
|
|
||||||
|
// loop over them
|
||||||
|
int windowIterator = 0;
|
||||||
|
while (windowsList.find("Window ") != std::string::npos) {
|
||||||
|
auto windowPropLen = windowsList.find("Window ", windowsList.find("\n\n") + 2);
|
||||||
|
if (windowPropLen == std::string::npos)
|
||||||
|
windowPropLen = windowsList.length();
|
||||||
|
const std::string windowProp = windowsList.substr(0, windowPropLen);
|
||||||
|
windowsList = windowsList.substr(windowPropLen);
|
||||||
|
|
||||||
|
// get window name
|
||||||
|
auto windowName = windowProp.substr(windowProp.find(" -> ") + 4);
|
||||||
|
windowName = windowName.substr(0, windowName.find_first_of('\n') - 1);
|
||||||
|
|
||||||
|
auto windowHandle = windowProp.substr(7, windowProp.find(" -> ") - 7);
|
||||||
|
|
||||||
|
QString text = QString::fromStdString("Window " + windowHandle + ": " + windowName);
|
||||||
|
|
||||||
|
QPushButton* button = new QPushButton(text, (QWidget*)WINDOWS_SCROLL_AREA_CONTENTS);
|
||||||
|
button->move(9, 5 + (BUTTON_HEIGHT + BUTTON_PAD) * windowIterator);
|
||||||
|
button->resize(BUTTON_WIDTH, BUTTON_HEIGHT);
|
||||||
|
QObject::connect(button, &QPushButton::clicked, [=] () {
|
||||||
|
std::string HANDLE = button->text().toStdString();
|
||||||
|
HANDLE = HANDLE.substr(7, HANDLE.find_first_of(':') - 7);
|
||||||
|
|
||||||
|
std::cout << "window:" << HANDLE << "\n";
|
||||||
|
pickerPtr->quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
windowIterator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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() - 1);
|
||||||
|
|
||||||
|
// now, get the screen
|
||||||
|
QScreen* pScreen = nullptr;
|
||||||
|
if (REGION.find_first_of(' ') == std::string::npos) {
|
||||||
|
std::cout << "error1\n";
|
||||||
|
pickerPtr->quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const auto SCREEN_NAME = REGION.substr(0, REGION.find_first_of(' '));
|
||||||
|
|
||||||
|
for (auto& screen : SCREENS) {
|
||||||
|
if (screen->name().toStdString() == SCREEN_NAME) {
|
||||||
|
pScreen = screen;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pScreen) {
|
||||||
|
std::cout << "error2\n";
|
||||||
|
pickerPtr->quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all the coords
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
const auto Y = std::stoi(REGION.substr(0, REGION.find_first_of(' ')));
|
||||||
|
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);
|
||||||
|
const auto H = std::stoi(REGION);
|
||||||
|
|
||||||
|
std::cout << "region:" << SCREEN_NAME << "@" << X - pScreen->geometry().x() << "," << Y - pScreen->geometry().y() << "," << W << "," << H << "\n";
|
||||||
|
pickerPtr->quit();
|
||||||
|
return 0;
|
||||||
|
} catch (...) {
|
||||||
|
std::cout << "error3\n";
|
||||||
|
pickerPtr->quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "error4\n";
|
||||||
|
pickerPtr->quit();
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
w.show();
|
||||||
|
return picker.exec();
|
||||||
|
}
|
19
hyprland-share-picker/mainpicker.cpp
Normal file
19
hyprland-share-picker/mainpicker.cpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#include "mainpicker.h"
|
||||||
|
#include "./ui_mainpicker.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
MainPicker::MainPicker(QWidget *parent)
|
||||||
|
: QMainWindow(parent)
|
||||||
|
, ui(new Ui::MainPicker)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainPicker::~MainPicker()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainPicker::onMonitorButtonClicked(QObject* target, QEvent* event) {
|
||||||
|
qDebug() << "click";
|
||||||
|
}
|
25
hyprland-share-picker/mainpicker.h
Normal file
25
hyprland-share-picker/mainpicker.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef MAINPICKER_H
|
||||||
|
#define MAINPICKER_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QEvent>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
namespace Ui { class MainPicker; }
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
class MainPicker : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
MainPicker(QWidget *parent = nullptr);
|
||||||
|
~MainPicker();
|
||||||
|
|
||||||
|
void onMonitorButtonClicked(QObject* target, QEvent* event);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::MainPicker *ui;
|
||||||
|
};
|
||||||
|
#endif // MAINPICKER_H
|
160
hyprland-share-picker/mainpicker.ui
Normal file
160
hyprland-share-picker/mainpicker.ui
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainPicker</class>
|
||||||
|
<widget class="QMainWindow" name="MainPicker">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>500</width>
|
||||||
|
<height>290</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>290</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>290</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>MainPicker</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>290</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>290</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>500</width>
|
||||||
|
<height>290</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>290</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>290</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="tabPosition">
|
||||||
|
<enum>QTabWidget::North</enum>
|
||||||
|
</property>
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="screens">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Screen</string>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>9</x>
|
||||||
|
<y>9</y>
|
||||||
|
<width>461</width>
|
||||||
|
<height>241</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>459</width>
|
||||||
|
<height>239</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>459</width>
|
||||||
|
<height>239</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::WheelFocus</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="windows">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Window</string>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QScrollArea" name="scrollArea_2">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>9</x>
|
||||||
|
<y>9</y>
|
||||||
|
<width>461</width>
|
||||||
|
<height>241</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents_2">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>459</width>
|
||||||
|
<height>239</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>459</width>
|
||||||
|
<height>239</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::WheelFocus</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="region">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Region</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>scrollArea</tabstop>
|
||||||
|
<tabstop>scrollArea_2</tabstop>
|
||||||
|
<tabstop>tabWidget</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -1,4 +1,4 @@
|
||||||
[portal]
|
[portal]
|
||||||
DBusName=org.freedesktop.impl.portal.desktop.wlr
|
DBusName=org.freedesktop.impl.portal.desktop.hyprland
|
||||||
Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;
|
Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;
|
||||||
UseIn=wlroots;sway;Wayfire;river;phosh;Hyprland;
|
UseIn=Hyprland;
|
30
meson.build
30
meson.build
|
@ -1,5 +1,5 @@
|
||||||
project(
|
project(
|
||||||
'xdg-desktop-portal-wlr',
|
'xdg-desktop-portal-hyprland',
|
||||||
'c',
|
'c',
|
||||||
version: '0.6.0',
|
version: '0.6.0',
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
|
@ -86,7 +86,7 @@ xdpw_files = files([
|
||||||
])
|
])
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
'xdg-desktop-portal-wlr',
|
'xdg-desktop-portal-hyprland',
|
||||||
[xdpw_files, wl_proto_files],
|
[xdpw_files, wl_proto_files],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
wayland_client,
|
wayland_client,
|
||||||
|
@ -111,7 +111,7 @@ conf_data.set('systemd_service', '')
|
||||||
systemd = dependency('systemd', required: get_option('systemd'))
|
systemd = dependency('systemd', required: get_option('systemd'))
|
||||||
|
|
||||||
if systemd.found()
|
if systemd.found()
|
||||||
systemd_service_file = 'xdg-desktop-portal-wlr.service'
|
systemd_service_file = 'xdg-desktop-portal-hyprland.service'
|
||||||
user_unit_dir = systemd.get_variable(pkgconfig: 'systemduserunitdir',
|
user_unit_dir = systemd.get_variable(pkgconfig: 'systemduserunitdir',
|
||||||
pkgconfig_define: ['prefix', get_option('prefix')])
|
pkgconfig_define: ['prefix', get_option('prefix')])
|
||||||
conf_data.set('systemd_service', 'SystemdService=' + systemd_service_file)
|
conf_data.set('systemd_service', 'SystemdService=' + systemd_service_file)
|
||||||
|
@ -126,33 +126,13 @@ endif
|
||||||
|
|
||||||
configure_file(
|
configure_file(
|
||||||
configuration: conf_data,
|
configuration: conf_data,
|
||||||
input: 'org.freedesktop.impl.portal.desktop.wlr.service.in',
|
input: 'org.freedesktop.impl.portal.desktop.hyprland.service.in',
|
||||||
output: '@BASENAME@',
|
output: '@BASENAME@',
|
||||||
install_dir: join_paths(get_option('datadir'), 'dbus-1', 'services'),
|
install_dir: join_paths(get_option('datadir'), 'dbus-1', 'services'),
|
||||||
)
|
)
|
||||||
|
|
||||||
install_data(
|
install_data(
|
||||||
'wlr.portal',
|
'hyprland.portal',
|
||||||
install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal', 'portals'),
|
install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal', 'portals'),
|
||||||
)
|
)
|
||||||
|
|
||||||
scdoc = dependency('scdoc', required: get_option('man-pages'), version: '>= 1.9.7', native: true)
|
|
||||||
if scdoc.found()
|
|
||||||
man_pages = ['xdg-desktop-portal-wlr.5.scd']
|
|
||||||
foreach src : man_pages
|
|
||||||
topic = src.split('.')[0]
|
|
||||||
section = src.split('.')[1]
|
|
||||||
output = topic + '.' + section
|
|
||||||
|
|
||||||
custom_target(
|
|
||||||
output,
|
|
||||||
input: files(src),
|
|
||||||
output: output,
|
|
||||||
command: [
|
|
||||||
'sh', '-c', '@0@ < @INPUT@ > @1@'.format(scdoc.get_variable(pkgconfig: 'scdoc'), output)
|
|
||||||
],
|
|
||||||
install: true,
|
|
||||||
install_dir: join_paths(get_option('mandir'), 'man' + section),
|
|
||||||
)
|
|
||||||
endforeach
|
|
||||||
endif
|
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library')
|
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')
|
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
|
||||||
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
|
|
||||||
|
|
4
org.freedesktop.impl.portal.desktop.hyprland.service.in
Normal file
4
org.freedesktop.impl.portal.desktop.hyprland.service.in
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[D-BUS Service]
|
||||||
|
Name=org.freedesktop.impl.portal.desktop.hyprland
|
||||||
|
Exec=@libexecdir@/xdg-desktop-portal-hyprland
|
||||||
|
@systemd_service@
|
|
@ -1,4 +0,0 @@
|
||||||
[D-BUS Service]
|
|
||||||
Name=org.freedesktop.impl.portal.desktop.wlr
|
|
||||||
Exec=@libexecdir@/xdg-desktop-portal-wlr
|
|
||||||
@systemd_service@
|
|
|
@ -333,202 +333,45 @@ static void wlr_init_xdg_outputs(struct xdpw_screencast_context *ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static pid_t spawn_chooser(char *cmd, int chooser_in[2], int chooser_out[2]) {
|
|
||||||
logprint(TRACE,
|
|
||||||
"exec chooser called: cmd %s, pipe chooser_in (%d,%d), pipe chooser_out (%d,%d)",
|
|
||||||
cmd, chooser_in[0], chooser_in[1], chooser_out[0], chooser_out[1]);
|
|
||||||
pid_t pid = fork();
|
|
||||||
|
|
||||||
if (pid < 0) {
|
|
||||||
perror("fork");
|
|
||||||
return pid;
|
|
||||||
} else if (pid == 0) {
|
|
||||||
close(chooser_in[1]);
|
|
||||||
close(chooser_out[0]);
|
|
||||||
|
|
||||||
dup2(chooser_in[0], STDIN_FILENO);
|
|
||||||
dup2(chooser_out[1], STDOUT_FILENO);
|
|
||||||
close(chooser_in[0]);
|
|
||||||
close(chooser_out[1]);
|
|
||||||
|
|
||||||
execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
|
|
||||||
|
|
||||||
perror("execl");
|
|
||||||
_exit(127);
|
|
||||||
}
|
|
||||||
|
|
||||||
close(chooser_in[0]);
|
|
||||||
close(chooser_out[1]);
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool wait_chooser(pid_t pid) {
|
|
||||||
int status;
|
|
||||||
if (waitpid(pid ,&status, 0) != -1 && WIFEXITED(status)) {
|
|
||||||
return WEXITSTATUS(status) != 127;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool wlr_output_chooser(struct xdpw_output_chooser *chooser,
|
|
||||||
struct wl_list *output_list, struct xdpw_wlr_output **output) {
|
|
||||||
logprint(DEBUG, "wlroots: output chooser called");
|
|
||||||
struct xdpw_wlr_output *out;
|
|
||||||
size_t name_size = 0;
|
|
||||||
char *name = NULL;
|
|
||||||
*output = NULL;
|
|
||||||
|
|
||||||
int chooser_in[2]; //p -> c
|
|
||||||
int chooser_out[2]; //c -> p
|
|
||||||
|
|
||||||
if (pipe(chooser_in) == -1) {
|
|
||||||
perror("pipe chooser_in");
|
|
||||||
logprint(ERROR, "Failed to open pipe chooser_in");
|
|
||||||
goto error_chooser_in;
|
|
||||||
}
|
|
||||||
if (pipe(chooser_out) == -1) {
|
|
||||||
perror("pipe chooser_out");
|
|
||||||
logprint(ERROR, "Failed to open pipe chooser_out");
|
|
||||||
goto error_chooser_out;
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t pid = spawn_chooser(chooser->cmd, chooser_in, chooser_out);
|
|
||||||
if (pid < 0) {
|
|
||||||
logprint(ERROR, "Failed to fork chooser");
|
|
||||||
goto error_fork;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (chooser->type) {
|
|
||||||
case XDPW_CHOOSER_DMENU:;
|
|
||||||
FILE *f = fdopen(chooser_in[1], "w");
|
|
||||||
if (f == NULL) {
|
|
||||||
perror("fdopen pipe chooser_in");
|
|
||||||
logprint(ERROR, "Failed to create stream writing to pipe chooser_in");
|
|
||||||
goto error_fork;
|
|
||||||
}
|
|
||||||
wl_list_for_each(out, output_list, link) {
|
|
||||||
fprintf(f, "%s\n", out->name);
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
close(chooser_in[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wait_chooser(pid)) {
|
|
||||||
close(chooser_out[0]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *f = fdopen(chooser_out[0], "r");
|
|
||||||
if (f == NULL) {
|
|
||||||
perror("fdopen pipe chooser_out");
|
|
||||||
logprint(ERROR, "Failed to create stream reading from pipe chooser_out");
|
|
||||||
close(chooser_out[0]);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t nread = getline(&name, &name_size, f);
|
|
||||||
fclose(f);
|
|
||||||
if (nread < 0) {
|
|
||||||
perror("getline failed");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Strip newline
|
|
||||||
char *p = strchr(name, '\n');
|
|
||||||
if (p != NULL) {
|
|
||||||
*p = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
logprint(TRACE, "wlroots: output chooser %s selects output %s", chooser->cmd, name);
|
|
||||||
wl_list_for_each(out, output_list, link) {
|
|
||||||
if (strcmp(out->name, name) == 0) {
|
|
||||||
*output = out;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(name);
|
|
||||||
|
|
||||||
end:
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error_fork:
|
|
||||||
close(chooser_out[0]);
|
|
||||||
close(chooser_out[1]);
|
|
||||||
error_chooser_out:
|
|
||||||
close(chooser_in[0]);
|
|
||||||
close(chooser_in[1]);
|
|
||||||
error_chooser_in:
|
|
||||||
*output = NULL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct xdpw_wlr_output *wlr_output_chooser_default(struct wl_list *output_list) {
|
|
||||||
logprint(DEBUG, "wlroots: output chooser called");
|
|
||||||
struct xdpw_output_chooser default_chooser[] = {
|
|
||||||
{XDPW_CHOOSER_SIMPLE, "slurp -f %o -or"},
|
|
||||||
{XDPW_CHOOSER_DMENU, "wofi -d -n --prompt='Select the monitor to share:'"},
|
|
||||||
{XDPW_CHOOSER_DMENU, "bemenu --prompt='Select the monitor to share:'"},
|
|
||||||
};
|
|
||||||
|
|
||||||
size_t N = sizeof(default_chooser)/sizeof(default_chooser[0]);
|
|
||||||
struct xdpw_wlr_output *output = NULL;
|
|
||||||
bool ret;
|
|
||||||
for (size_t i = 0; i<N; i++) {
|
|
||||||
ret = wlr_output_chooser(&default_chooser[i], output_list, &output);
|
|
||||||
if (!ret) {
|
|
||||||
logprint(DEBUG, "wlroots: output chooser %s not found. Trying next one.",
|
|
||||||
default_chooser[i].cmd);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (output != NULL) {
|
|
||||||
logprint(DEBUG, "wlroots: output chooser selects %s", output->name);
|
|
||||||
} else {
|
|
||||||
logprint(DEBUG, "wlroots: output chooser canceled");
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
return xdpw_wlr_output_first(output_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct xdpw_wlr_output *xdpw_wlr_output_chooser(struct xdpw_screencast_context *ctx) {
|
struct xdpw_wlr_output *xdpw_wlr_output_chooser(struct xdpw_screencast_context *ctx) {
|
||||||
switch (ctx->state->config->screencast_conf.chooser_type) {
|
char result[1024];
|
||||||
case XDPW_CHOOSER_DEFAULT:
|
FILE *fp;
|
||||||
return wlr_output_chooser_default(&ctx->output_list);
|
char buf[1024];
|
||||||
case XDPW_CHOOSER_NONE:
|
|
||||||
if (ctx->state->config->screencast_conf.output_name) {
|
fp = popen("/usr/bin/hyprland-share-picker", "r");
|
||||||
return xdpw_wlr_output_find_by_name(&ctx->output_list, ctx->state->config->screencast_conf.output_name);
|
if (fp == NULL) {
|
||||||
} else {
|
printf("Failed to run command\n");
|
||||||
return xdpw_wlr_output_first(&ctx->output_list);
|
exit(1);
|
||||||
}
|
}
|
||||||
case XDPW_CHOOSER_DMENU:
|
|
||||||
case XDPW_CHOOSER_SIMPLE:;
|
while (fgets(buf, sizeof(buf), fp) != NULL) {
|
||||||
struct xdpw_wlr_output *output = NULL;
|
strcat(result, buf);
|
||||||
if (!ctx->state->config->screencast_conf.chooser_cmd) {
|
}
|
||||||
logprint(ERROR, "wlroots: no output chooser given");
|
|
||||||
goto end;
|
pclose(fp);
|
||||||
}
|
|
||||||
struct xdpw_output_chooser chooser = {
|
// great, let's parse it.
|
||||||
ctx->state->config->screencast_conf.chooser_type,
|
|
||||||
ctx->state->config->screencast_conf.chooser_cmd
|
// TODO: window & region
|
||||||
};
|
if (strncmp(result, "screen:", 7) == 0) {
|
||||||
logprint(DEBUG, "wlroots: output chooser %s (%d)", chooser.cmd, chooser.type);
|
// find
|
||||||
bool ret = wlr_output_chooser(&chooser, &ctx->output_list, &output);
|
char* display_name = malloc(strlen(result) - 7);
|
||||||
if (!ret) {
|
strncpy(display_name, result + 7, strlen(result) - 8);
|
||||||
logprint(ERROR, "wlroots: output chooser %s failed", chooser.cmd);
|
display_name[strlen(result) - 8] = 0;
|
||||||
goto end;
|
|
||||||
}
|
struct xdpw_wlr_output* out;
|
||||||
if (output) {
|
wl_list_for_each(out, &ctx->output_list, link) {
|
||||||
logprint(DEBUG, "wlroots: output chooser selects %s", output->name);
|
if (strcmp(out->name, display_name) == 0) {
|
||||||
} else {
|
break;
|
||||||
logprint(DEBUG, "wlroots: output chooser canceled");
|
}
|
||||||
}
|
}
|
||||||
return output;
|
|
||||||
|
free(display_name);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
end:
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct xdpw_wlr_output *xdpw_wlr_output_first(struct wl_list *output_list) {
|
struct xdpw_wlr_output *xdpw_wlr_output_first(struct wl_list *output_list) {
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
xdg-desktop-portal-wlr(5)
|
|
||||||
|
|
||||||
# NAME
|
|
||||||
|
|
||||||
xdg-desktop-portal-wlr - an xdg-desktop-portal backend for wlroots
|
|
||||||
|
|
||||||
# DESCRIPTION
|
|
||||||
|
|
||||||
xdg-desktop-portal-wlr (or xdpw for short) allows applications to request
|
|
||||||
screenshots and screencasts via xdg-desktop-portal in wlroots-based Wayland
|
|
||||||
compositors.
|
|
||||||
|
|
||||||
xdpw will try to load the configuration file from these locations:
|
|
||||||
|
|
||||||
- $XDG_CONFIG_HOME/xdg-desktop-portal-wlr/$XDG_CURRENT_DESKTOP
|
|
||||||
- $XDG_CONFIG_HOME/xdg-desktop-portal-wlr/config
|
|
||||||
- /etc/xdg/xdg-desktop-portal-wlr/$XDG_CURRENT_DESKTOP
|
|
||||||
- /etc/xdg/xdg-desktop-portal-wlr/config
|
|
||||||
|
|
||||||
_$XDG_CONFIG_HOME_ defaults to _~/.config_.
|
|
||||||
_$XDG_CURRENT_DESKTOP_ can be a colon separated list. Each element of that list will be tried.
|
|
||||||
|
|
||||||
The configuration files use the INI file format. Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
[screencast]
|
|
||||||
output_name=HDMI-A-1
|
|
||||||
max_fps=30
|
|
||||||
exec_before=disable_notifications.sh
|
|
||||||
exec_after=enable_notifications.sh
|
|
||||||
chooser_type=simple
|
|
||||||
chooser_cmd=slurp -f %o -or
|
|
||||||
```
|
|
||||||
|
|
||||||
# SCREENCAST OPTIONS
|
|
||||||
|
|
||||||
These options need to be placed under the **[screencast]** section.
|
|
||||||
|
|
||||||
**output_name** = _name_
|
|
||||||
Select which output will be screencast.
|
|
||||||
|
|
||||||
This option is used with **chooser_type** = none. The list of available outputs
|
|
||||||
can be obtained via **wayland-info**(1) (under the _zxdg_output_manager_v1_
|
|
||||||
section).
|
|
||||||
|
|
||||||
**max_fps** = _limit_
|
|
||||||
Limit the number of frames per second to the provided rate.
|
|
||||||
|
|
||||||
This is useful to reduce CPU usage when capturing frames at the output's
|
|
||||||
refresh rate is unnecessary.
|
|
||||||
|
|
||||||
**exec_before** = _command_
|
|
||||||
Execute _command_ before starting a screencast. The command will be executed within sh.
|
|
||||||
|
|
||||||
**exec_after** = _command_
|
|
||||||
Execute _command_ after ending all screencasts. The command will be executed within sh.
|
|
||||||
|
|
||||||
**chooser_cmd** = _command_
|
|
||||||
Run this command to select an output.
|
|
||||||
|
|
||||||
For more details see **OUTPUT CHOOSER**.
|
|
||||||
|
|
||||||
**chooser_type** = _type_
|
|
||||||
Specifies the input send to the chooser.
|
|
||||||
|
|
||||||
The supported types are:
|
|
||||||
- default: xdpw will try to use the first chooser found in the list of hardcoded choosers
|
|
||||||
(slurp, wofi, bemenu) and will fallback to an arbitrary output if none of those were found.
|
|
||||||
- none: xdpw will allow screencast either on the output given by **output_name**, or if empty
|
|
||||||
an arbitrary output without further interaction.
|
|
||||||
- simple, dmenu: xdpw will launch the chooser given by **chooser_cmd**. For more details
|
|
||||||
see **OUTPUT CHOOSER**.
|
|
||||||
|
|
||||||
**force_mod_linear** = _bool_
|
|
||||||
Force buffers with implicit modifiers to be linear (experimental)
|
|
||||||
|
|
||||||
Setting this option to 1 will force xdpw to allocate dma-bufs with implicit modifier as linear.
|
|
||||||
This option shouldn't be required on single gpu setups, but can increase compatibility
|
|
||||||
especially on setups with multiple gpus.
|
|
||||||
|
|
||||||
This option is experimental and can be removed or replaced in future versions.
|
|
||||||
|
|
||||||
## OUTPUT CHOOSER
|
|
||||||
|
|
||||||
The chooser can be any program or script with the following behaviour:
|
|
||||||
- It returns any error code except 127. The error code 127 is internally used to signal
|
|
||||||
that no command could be found and all output from it will be ignored.
|
|
||||||
- It returns the name of a valid output on stdout as given by **wayland-info**(1).
|
|
||||||
Everything else will be handled as declined by the user.
|
|
||||||
- To signal that the user has declined screencast, the chooser should exit without
|
|
||||||
anything on stdout.
|
|
||||||
|
|
||||||
Supported types of choosers via the **chooser_type** option:
|
|
||||||
- simple: the chooser is just called without anything further on stdin.
|
|
||||||
- dmenu: the chooser receives a newline separated list (dmenu style) of outputs on stdin.
|
|
||||||
|
|
||||||
# SEE ALSO
|
|
||||||
|
|
||||||
**pipewire**(1)
|
|
Loading…
Reference in a new issue