mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-11-23 22:55: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/
|
||||
build-*/
|
||||
|
||||
.vscode/
|
||||
|
||||
hyprland-share-picker/build/
|
|
@ -13,6 +13,7 @@ Although `-wlr` **does** work with Hyprland, `-hyprland` offers more features.
|
|||
```sh
|
||||
meson build
|
||||
ninja -C build
|
||||
cd hyprland-share-picker && make all && cd ..
|
||||
```
|
||||
|
||||
## Installing
|
||||
|
@ -21,6 +22,7 @@ ninja -C build
|
|||
|
||||
```sh
|
||||
ninja -C build install
|
||||
sudo cp ./hyprland-share-picker/build/hyprland-share-picker /usr/bin
|
||||
```
|
||||
|
||||
## 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]
|
||||
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;
|
||||
UseIn=wlroots;sway;Wayfire;river;phosh;Hyprland;
|
||||
UseIn=Hyprland;
|
30
meson.build
30
meson.build
|
@ -1,5 +1,5 @@
|
|||
project(
|
||||
'xdg-desktop-portal-wlr',
|
||||
'xdg-desktop-portal-hyprland',
|
||||
'c',
|
||||
version: '0.6.0',
|
||||
license: 'MIT',
|
||||
|
@ -86,7 +86,7 @@ xdpw_files = files([
|
|||
])
|
||||
|
||||
executable(
|
||||
'xdg-desktop-portal-wlr',
|
||||
'xdg-desktop-portal-hyprland',
|
||||
[xdpw_files, wl_proto_files],
|
||||
dependencies: [
|
||||
wayland_client,
|
||||
|
@ -111,7 +111,7 @@ conf_data.set('systemd_service', '')
|
|||
systemd = dependency('systemd', required: get_option('systemd'))
|
||||
|
||||
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',
|
||||
pkgconfig_define: ['prefix', get_option('prefix')])
|
||||
conf_data.set('systemd_service', 'SystemdService=' + systemd_service_file)
|
||||
|
@ -126,33 +126,13 @@ endif
|
|||
|
||||
configure_file(
|
||||
configuration: conf_data,
|
||||
input: 'org.freedesktop.impl.portal.desktop.wlr.service.in',
|
||||
input: 'org.freedesktop.impl.portal.desktop.hyprland.service.in',
|
||||
output: '@BASENAME@',
|
||||
install_dir: join_paths(get_option('datadir'), 'dbus-1', 'services'),
|
||||
)
|
||||
|
||||
install_data(
|
||||
'wlr.portal',
|
||||
'hyprland.portal',
|
||||
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('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) {
|
||||
switch (ctx->state->config->screencast_conf.chooser_type) {
|
||||
case XDPW_CHOOSER_DEFAULT:
|
||||
return wlr_output_chooser_default(&ctx->output_list);
|
||||
case XDPW_CHOOSER_NONE:
|
||||
if (ctx->state->config->screencast_conf.output_name) {
|
||||
return xdpw_wlr_output_find_by_name(&ctx->output_list, ctx->state->config->screencast_conf.output_name);
|
||||
char result[1024];
|
||||
FILE *fp;
|
||||
char buf[1024];
|
||||
|
||||
fp = popen("/usr/bin/hyprland-share-picker", "r");
|
||||
if (fp == NULL) {
|
||||
printf("Failed to run command\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (fgets(buf, sizeof(buf), fp) != NULL) {
|
||||
strcat(result, buf);
|
||||
}
|
||||
|
||||
pclose(fp);
|
||||
|
||||
// great, let's parse it.
|
||||
|
||||
// TODO: window & region
|
||||
if (strncmp(result, "screen:", 7) == 0) {
|
||||
// find
|
||||
char* display_name = malloc(strlen(result) - 7);
|
||||
strncpy(display_name, result + 7, strlen(result) - 8);
|
||||
display_name[strlen(result) - 8] = 0;
|
||||
|
||||
struct xdpw_wlr_output* out;
|
||||
wl_list_for_each(out, &ctx->output_list, link) {
|
||||
if (strcmp(out->name, display_name) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(display_name);
|
||||
|
||||
return out;
|
||||
} else {
|
||||
return xdpw_wlr_output_first(&ctx->output_list);
|
||||
}
|
||||
case XDPW_CHOOSER_DMENU:
|
||||
case XDPW_CHOOSER_SIMPLE:;
|
||||
struct xdpw_wlr_output *output = NULL;
|
||||
if (!ctx->state->config->screencast_conf.chooser_cmd) {
|
||||
logprint(ERROR, "wlroots: no output chooser given");
|
||||
goto end;
|
||||
}
|
||||
struct xdpw_output_chooser chooser = {
|
||||
ctx->state->config->screencast_conf.chooser_type,
|
||||
ctx->state->config->screencast_conf.chooser_cmd
|
||||
};
|
||||
logprint(DEBUG, "wlroots: output chooser %s (%d)", chooser.cmd, chooser.type);
|
||||
bool ret = wlr_output_chooser(&chooser, &ctx->output_list, &output);
|
||||
if (!ret) {
|
||||
logprint(ERROR, "wlroots: output chooser %s failed", chooser.cmd);
|
||||
goto end;
|
||||
}
|
||||
if (output) {
|
||||
logprint(DEBUG, "wlroots: output chooser selects %s", output->name);
|
||||
} else {
|
||||
logprint(DEBUG, "wlroots: output chooser canceled");
|
||||
}
|
||||
return output;
|
||||
}
|
||||
end:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
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