Skip to content

Commit aa7f2f4

Browse files
committed
input-capture: impl portal
1 parent a10726d commit aa7f2f4

File tree

14 files changed

+601
-42
lines changed

14 files changed

+601
-42
lines changed

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pkg_check_modules(
6262
libpipewire-0.3>=1.1.82
6363
libspa-0.2
6464
libdrm
65+
uuid
6566
gbm
6667
hyprlang>=0.2.0
6768
hyprutils>=0.2.6
@@ -132,6 +133,8 @@ protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-toplevel-export-v1"
132133
true)
133134
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-toplevel-mapping-v1"
134135
true)
136+
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-input-capture-v1"
137+
true)
135138
protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
136139
protocolnew("staging/ext-foreign-toplevel-list" "ext-foreign-toplevel-list-v1" false)
137140

hyprland-share-picker/meson.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ executable('hyprland-share-picker',
1818
sources,
1919
ui_files,
2020
moc,
21-
dependencies: qtdep,
21+
dependencies: [qtdep, dependency('hyprutils')],
2222
install: true
2323
)

hyprland.portal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[portal]
22
DBusName=org.freedesktop.impl.portal.desktop.hyprland
3-
Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.GlobalShortcuts;
3+
Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.GlobalShortcuts;org.freedesktop.impl.portal.InputCapture;
44
UseIn=wlroots;Hyprland;sway;Wayfire;river;

meson.build

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ install_data(
6161
'hyprland.portal',
6262
install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal', 'portals'),
6363
)
64+
version = run_command('cat', files('VERSION'), check: true).stdout().strip()
65+
66+
add_project_arguments(f'-DXDPH_VERSION="@version@"', language : 'cpp')
6467

6568
inc = include_directories('.', 'protocols')
6669

protocols/meson.build

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ wayland_protos = dependency('wayland-protocols',
44
)
55

66
hyprland_protos = dependency('hyprland-protocols',
7-
version: '>=0.6.4',
7+
version: '>=0.7.0',
88
fallback: 'hyprland-protocols',
99
)
1010

@@ -23,6 +23,7 @@ client_protocols = [
2323
hl_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml',
2424
hl_protocol_dir / 'protocols/hyprland-toplevel-mapping-v1.xml',
2525
hl_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml',
26+
hl_protocol_dir / 'protocols/hyprland-input-capture-v1.xml',
2627
wl_protocol_dir / 'stable/linux-dmabuf/linux-dmabuf-v1.xml',
2728
wl_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml',
2829
]

src/core/PortalManager.cpp

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include "../helpers/MiscFunctions.hpp"
44

55
#include <pipewire/pipewire.h>
6-
#include <poll.h>
76
#include <sys/mman.h>
87
#include <fcntl.h>
98
#include <unistd.h>
@@ -19,12 +18,21 @@ SOutput::SOutput(SP<CCWlOutput> output_) : output(output_) {
1918

2019
Debug::log(LOG, "Found output name {}", name);
2120
});
22-
output->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { //
21+
output->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width_, int32_t height_, int32_t refresh) {
2322
refreshRate = refresh;
23+
width = width_;
24+
height = height_;
2425
});
25-
output->setGeometry([this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model,
26-
int32_t transform_) { //
27-
transform = (wl_output_transform)transform_;
26+
output->setGeometry(
27+
[this](CCWlOutput* r, int32_t x_, int32_t y_, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform_) {
28+
transform = (wl_output_transform)transform_;
29+
x = x_;
30+
y = y_;
31+
});
32+
output->setScale([this](CCWlOutput* r, uint32_t factor_) { scale = factor_; });
33+
output->setDone([](CCWlOutput* r) {
34+
if (g_pPortalManager->m_sPortals.inputCapture != nullptr)
35+
g_pPortalManager->m_sPortals.inputCapture->zonesChanged();
2836
});
2937
}
3038

@@ -63,7 +71,9 @@ void CPortalManager::onGlobal(uint32_t name, const char* interface, uint32_t ver
6371
m_sPortals.globalShortcuts = std::make_unique<CGlobalShortcutsPortal>(makeShared<CCHyprlandGlobalShortcutsManagerV1>(
6472
(wl_proxy*)wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &hyprland_global_shortcuts_manager_v1_interface, version)));
6573
}
66-
74+
if (INTERFACE == hyprland_input_capture_manager_v1_interface.name)
75+
m_sPortals.inputCapture = std::make_unique<CInputCapturePortal>(makeShared<CCHyprlandInputCaptureManagerV1>(
76+
(wl_proxy*)wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &hyprland_input_capture_manager_v1_interface, version)));
6777
else if (INTERFACE == hyprland_toplevel_export_manager_v1_interface.name) {
6878
m_sWaylandConnection.hyprlandToplevelMgr = makeShared<CCHyprlandToplevelExportManagerV1>(
6979
(wl_proxy*)wl_registry_bind((wl_registry*)m_sWaylandConnection.registry->resource(), name, &hyprland_toplevel_export_manager_v1_interface, version));
@@ -293,32 +303,21 @@ void CPortalManager::init() {
293303
}
294304

295305
void CPortalManager::startEventLoop() {
306+
addFdToEventLoop(m_pConnection->getEventLoopPollData().fd, POLLIN, nullptr);
307+
addFdToEventLoop(wl_display_get_fd(m_sWaylandConnection.display), POLLIN, nullptr);
308+
addFdToEventLoop(pw_loop_get_fd(m_sPipewire.loop), POLLIN, nullptr);
296309

297-
pollfd pollfds[] = {
298-
{
299-
.fd = m_pConnection->getEventLoopPollData().fd,
300-
.events = POLLIN,
301-
},
302-
{
303-
.fd = wl_display_get_fd(m_sWaylandConnection.display),
304-
.events = POLLIN,
305-
},
306-
{
307-
.fd = pw_loop_get_fd(m_sPipewire.loop),
308-
.events = POLLIN,
309-
},
310-
};
311-
312-
std::thread pollThr([this, &pollfds]() {
310+
std::thread pollThr([this]() {
313311
while (1) {
314-
int ret = poll(pollfds, 3, 5000 /* 5 seconds, reasonable. It's because we might need to terminate */);
312+
313+
int ret = poll(m_sEventLoopInternals.pollFds.data(), m_sEventLoopInternals.pollFds.size(), 5000 /* 5 seconds, reasonable. It's because we might need to terminate */);
315314
if (ret < 0) {
316315
Debug::log(CRIT, "[core] Polling fds failed with {}", strerror(errno));
317316
g_pPortalManager->terminate();
318317
}
319318

320319
for (size_t i = 0; i < 3; ++i) {
321-
if (pollfds[i].revents & POLLHUP) {
320+
if (m_sEventLoopInternals.pollFds.data()->revents & POLLHUP) {
322321
Debug::log(CRIT, "[core] Disconnected from pollfd id {}", i);
323322
g_pPortalManager->terminate();
324323
}
@@ -391,13 +390,13 @@ void CPortalManager::startEventLoop() {
391390

392391
m_mEventLock.lock();
393392

394-
if (pollfds[0].revents & POLLIN /* dbus */) {
393+
if (m_sEventLoopInternals.pollFds[0].revents & POLLIN /* dbus */) {
395394
while (m_pConnection->processPendingEvent()) {
396395
;
397396
}
398397
}
399398

400-
if (pollfds[1].revents & POLLIN /* wl */) {
399+
if (m_sEventLoopInternals.pollFds[1].revents & POLLIN /* wl */) {
401400
wl_display_flush(m_sWaylandConnection.display);
402401
if (wl_display_prepare_read(m_sWaylandConnection.display) == 0) {
403402
wl_display_read_events(m_sWaylandConnection.display);
@@ -407,12 +406,18 @@ void CPortalManager::startEventLoop() {
407406
}
408407
}
409408

410-
if (pollfds[2].revents & POLLIN /* pw */) {
409+
if (m_sEventLoopInternals.pollFds[2].revents & POLLIN /* pw */) {
411410
while (pw_loop_iterate(m_sPipewire.loop, 0) != 0) {
412411
;
413412
}
414413
}
415414

415+
for (pollfd p : m_sEventLoopInternals.pollFds) {
416+
if (p.revents & POLLIN && m_sEventLoopInternals.pollCallbacks.contains(p.fd)) {
417+
m_sEventLoopInternals.pollCallbacks[p.fd]();
418+
}
419+
}
420+
416421
std::vector<CTimer*> toRemove;
417422
for (auto& t : m_sTimersThread.timers) {
418423
if (t->passed()) {
@@ -441,6 +446,7 @@ void CPortalManager::startEventLoop() {
441446
m_sPortals.screencopy.reset();
442447
m_sPortals.screenshot.reset();
443448
m_sHelpers.toplevel.reset();
449+
m_sPortals.inputCapture.reset();
444450

445451
m_pConnection.reset();
446452
pw_loop_destroy(m_sPipewire.loop);
@@ -462,6 +468,10 @@ SOutput* CPortalManager::getOutputFromName(const std::string& name) {
462468
return nullptr;
463469
}
464470

471+
std::vector<std::unique_ptr<SOutput>> const& CPortalManager::getAllOutputs() {
472+
return m_vOutputs;
473+
}
474+
465475
static char* gbm_find_render_node(drmDevice* device) {
466476
drmDevice* devices[64];
467477
char* render_node = NULL;
@@ -511,6 +521,20 @@ void CPortalManager::addTimer(const CTimer& timer) {
511521
m_sTimersThread.loopSignal.notify_all();
512522
}
513523

524+
void CPortalManager::addFdToEventLoop(int fd, short events, std::function<void()> callback) {
525+
m_sEventLoopInternals.pollFds.emplace_back(pollfd{.fd = fd, .events = POLLIN});
526+
527+
if (callback == nullptr)
528+
return;
529+
530+
m_sEventLoopInternals.pollCallbacks[fd] = callback;
531+
}
532+
533+
void CPortalManager::removeFdFromEventLoop(int fd) {
534+
std::erase_if(m_sEventLoopInternals.pollFds, [fd](const pollfd& p) { return p.fd == fd; });
535+
m_sEventLoopInternals.pollCallbacks.erase(fd);
536+
}
537+
514538
void CPortalManager::terminate() {
515539
m_bTerminate = true;
516540

src/core/PortalManager.hpp

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
#include "../portals/Screencopy.hpp"
99
#include "../portals/Screenshot.hpp"
1010
#include "../portals/GlobalShortcuts.hpp"
11+
#include "../portals/InputCapture.hpp"
1112
#include "../helpers/Timer.hpp"
1213
#include "../shared/ToplevelManager.hpp"
1314
#include "../shared/ToplevelMappingManager.hpp"
1415
#include <gbm.h>
16+
#include <poll.h>
1517
#include <xf86drm.h>
1618

1719
#include "hyprland-toplevel-export-v1.hpp"
@@ -34,6 +36,11 @@ struct SOutput {
3436
uint32_t id = 0;
3537
float refreshRate = 60.0;
3638
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
39+
uint32_t width = 0;
40+
uint32_t height = 0;
41+
int32_t x = 0;
42+
int32_t y = 0;
43+
int32_t scale = 1;
3744
};
3845

3946
struct SDMABUFModifier {
@@ -45,13 +52,14 @@ class CPortalManager {
4552
public:
4653
CPortalManager();
4754

48-
void init();
55+
void init();
4956

50-
void onGlobal(uint32_t name, const char* interface, uint32_t version);
51-
void onGlobalRemoved(uint32_t name);
57+
void onGlobal(uint32_t name, const char* interface, uint32_t version);
58+
void onGlobalRemoved(uint32_t name);
5259

53-
sdbus::IConnection* getConnection();
54-
SOutput* getOutputFromName(const std::string& name);
60+
sdbus::IConnection* getConnection();
61+
SOutput* getOutputFromName(const std::string& name);
62+
std::vector<std::unique_ptr<SOutput>> const& getAllOutputs();
5563

5664
struct {
5765
pw_loop* loop = nullptr;
@@ -61,6 +69,7 @@ class CPortalManager {
6169
std::unique_ptr<CScreencopyPortal> screencopy;
6270
std::unique_ptr<CScreenshotPortal> screenshot;
6371
std::unique_ptr<CGlobalShortcutsPortal> globalShortcuts;
72+
std::unique_ptr<CInputCapturePortal> inputCapture;
6473
} m_sPortals;
6574

6675
struct {
@@ -95,6 +104,9 @@ class CPortalManager {
95104

96105
gbm_device* createGBMDevice(drmDevice* dev);
97106

107+
void addFdToEventLoop(int fd, short events, std::function<void()> callback);
108+
void removeFdFromEventLoop(int fd);
109+
98110
// terminate after the event loop has been created. Before we can exit()
99111
void terminate();
100112

@@ -105,10 +117,12 @@ class CPortalManager {
105117
pid_t m_iPID = 0;
106118

107119
struct {
108-
std::condition_variable loopSignal;
109-
std::mutex loopMutex;
110-
std::atomic<bool> shouldProcess = false;
111-
std::mutex loopRequestMutex;
120+
std::condition_variable loopSignal;
121+
std::mutex loopMutex;
122+
std::atomic<bool> shouldProcess = false;
123+
std::mutex loopRequestMutex;
124+
std::vector<pollfd> pollFds;
125+
std::map<int, std::function<void()>> pollCallbacks;
112126
} m_sEventLoopInternals;
113127

114128
struct {

src/helpers/MiscFunctions.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
#include <vector>
88
#include <string>
99
#include <algorithm>
10+
#include <sys/mman.h>
11+
#include <sys/stat.h>
12+
#include <fcntl.h>
13+
#include <uuid/uuid.h>
1014

1115
#include <hyprutils/os/Process.hpp>
1216
using namespace Hyprutils::OS;
@@ -55,3 +59,84 @@ bool inShellPath(const std::string& exec) {
5559

5660
return std::ranges::any_of(paths, [&exec](std::string& path) { return access((path + "/" + exec).c_str(), X_OK) == 0; });
5761
}
62+
63+
std::string getRandomUUID() {
64+
std::string uuid;
65+
uuid_t uuid_;
66+
uuid_generate_random(uuid_);
67+
return std::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", (uint16_t)uuid_[0], (uint16_t)uuid_[1],
68+
(uint16_t)uuid_[2], (uint16_t)uuid_[3], (uint16_t)uuid_[4], (uint16_t)uuid_[5], (uint16_t)uuid_[6], (uint16_t)uuid_[7], (uint16_t)uuid_[8],
69+
(uint16_t)uuid_[9], (uint16_t)uuid_[10], (uint16_t)uuid_[11], (uint16_t)uuid_[12], (uint16_t)uuid_[13], (uint16_t)uuid_[14], (uint16_t)uuid_[15]);
70+
}
71+
72+
std::pair<int, std::string> openExclusiveShm() {
73+
// Only absolute paths can be shared across different shm_open() calls
74+
std::string name = "/" + getRandomUUID();
75+
76+
for (size_t i = 0; i < 69; ++i) {
77+
int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600);
78+
if (fd >= 0)
79+
return {fd, name};
80+
}
81+
82+
return {-1, ""};
83+
}
84+
85+
int allocateSHMFile(size_t len) {
86+
auto [fd, name] = openExclusiveShm();
87+
if (fd < 0)
88+
return -1;
89+
90+
shm_unlink(name.c_str());
91+
92+
int ret;
93+
do {
94+
ret = ftruncate(fd, len);
95+
} while (ret < 0 && errno == EINTR);
96+
97+
if (ret < 0) {
98+
close(fd);
99+
return -1;
100+
}
101+
102+
return fd;
103+
}
104+
105+
bool allocateSHMFilePair(size_t size, int* rw_fd_ptr, int* ro_fd_ptr) {
106+
auto [fd, name] = openExclusiveShm();
107+
if (fd < 0) {
108+
return false;
109+
}
110+
111+
// CLOEXEC is guaranteed to be set by shm_open
112+
int ro_fd = shm_open(name.c_str(), O_RDONLY, 0);
113+
if (ro_fd < 0) {
114+
shm_unlink(name.c_str());
115+
close(fd);
116+
return false;
117+
}
118+
119+
shm_unlink(name.c_str());
120+
121+
// Make sure the file cannot be re-opened in read-write mode (e.g. via
122+
// "/proc/self/fd/" on Linux)
123+
if (fchmod(fd, 0) != 0) {
124+
close(fd);
125+
close(ro_fd);
126+
return false;
127+
}
128+
129+
int ret;
130+
do {
131+
ret = ftruncate(fd, size);
132+
} while (ret < 0 && errno == EINTR);
133+
if (ret < 0) {
134+
close(fd);
135+
close(ro_fd);
136+
return false;
137+
}
138+
139+
*rw_fd_ptr = fd;
140+
*ro_fd_ptr = ro_fd;
141+
return true;
142+
}

0 commit comments

Comments
 (0)