Implement hyprland-share-picker

This commit is contained in:
vaxerski 2022-12-03 21:27:49 +00:00
parent f2e64940ae
commit 80775707ea
17 changed files with 510 additions and 336 deletions

4
.gitignore vendored
View file

@ -54,3 +54,7 @@ dkms.conf
# build folder # build folder
build/ build/
build-*/ build-*/
.vscode/
hyprland-share-picker/build/

View file

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

View 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

View file

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

View 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()

View 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)

View 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();
}

View 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";
}

View 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

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,4 @@
[D-BUS Service]
Name=org.freedesktop.impl.portal.desktop.hyprland
Exec=@libexecdir@/xdg-desktop-portal-hyprland
@systemd_service@

View file

@ -1,4 +0,0 @@
[D-BUS Service]
Name=org.freedesktop.impl.portal.desktop.wlr
Exec=@libexecdir@/xdg-desktop-portal-wlr
@systemd_service@

View file

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

View file

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