Skip to content

Commit 5f49df8

Browse files
committed
Add Wayland screen capture backend
1 parent 8bae3af commit 5f49df8

8 files changed

Lines changed: 823 additions & 4 deletions

.gitlab-ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ linux-builder:
4141
- export LD_LIBRARY_PATH="$OPENCV_ROOT/lib:$LD_LIBRARY_PATH"
4242
- export PATH="$OPENCV_ROOT/bin:$PATH"
4343
- mkdir -p build; cd build;
44-
- cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR/build/install-x64" -D"OpenCV_DIR:PATH=$OpenCV_DIR" -D"PYTHON_MODULE_PATH=python" -D"RUBY_MODULE_PATH=ruby" -DCMAKE_BUILD_TYPE:STRING=Release -DAPPIMAGE_BUILD=1 -DUSE_SYSTEM_JSONCPP=0 ../
44+
- pkg-config --exists libpipewire-0.3 libspa-0.2 gio-2.0 gio-unix-2.0
45+
- cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR/build/install-x64" -D"OpenCV_DIR:PATH=$OpenCV_DIR" -D"PYTHON_MODULE_PATH=python" -D"RUBY_MODULE_PATH=ruby" -DCMAKE_BUILD_TYPE:STRING=Release -DAPPIMAGE_BUILD=1 -DUSE_SYSTEM_JSONCPP=0 -DENABLE_WAYLAND_CAPTURE=ON ../
4546
- cmake --build . --parallel $(nproc)
4647
- make install
4748
- ctest --output-on-failure -VV

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ option(USE_SYSTEM_JSONCPP "Use system installed JsonCpp, if found" ON)
7272
option(DISABLE_BUNDLED_JSONCPP "Don't fall back to bundled JsonCpp" OFF)
7373
option(ENABLE_MAGICK "Use ImageMagick, if available" ON)
7474
option(ENABLE_OPENCV "Build with OpenCV algorithms (requires Boost, Protobuf 3)" ON)
75+
option(ENABLE_WAYLAND_CAPTURE "Build Wayland screen capture support with xdg-desktop-portal and PipeWire (Linux only)" ON)
7576
option(USE_HW_ACCEL "Enable hardware-accelerated encoding-decoding with FFmpeg 3.4+" ON)
7677
option(ENABLE_VULKAN_BENCHMARK "Build the experimental Vulkan benchmark example" OFF)
7778

doc/INSTALL-LINUX.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ list below to help distinguish between them.
3434
* http://www.ffmpeg.org/ `(Library)`
3535
* This library is used to decode and encode video, audio, and image files. It is also used to obtain information about media files, such as frame rate, sample rate, aspect ratio, and other common attributes.
3636

37+
### PipeWire and xdg-desktop-portal (libpipewire, libspa, GIO)
38+
* https://pipewire.org/ `(Library)`
39+
* https://flatpak.github.io/xdg-desktop-portal/ `(Runtime Service)`
40+
* These libraries and services are used for Wayland screen capture. The development packages are needed at build time, and a desktop portal implementation must be available at runtime.
41+
3742
### ImageMagick++ (libMagick++, libMagickWand, libMagickCore)
3843
* http://www.imagemagick.org/script/magick++.php `(Library)`
3944
* This library is **optional**, and used to decode and encode images.
@@ -154,6 +159,8 @@ software packages available to download and install.
154159
libjsoncpp-dev \
155160
libmagick++-dev \
156161
libopenshot-audio-dev \
162+
libpipewire-0.3-dev \
163+
libspa-0.2-dev \
157164
libswscale-dev \
158165
libunittest++-dev \
159166
libxcursor-dev \
@@ -165,6 +172,8 @@ software packages available to download and install.
165172
qtbase5-dev \
166173
qtmultimedia5-dev \
167174
swig \
175+
xdg-desktop-portal \
176+
xdg-desktop-portal-gtk \
168177
python3-zmq \
169178
python3-pyqt5.qtwebengine
170179

src/CMakeLists.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,21 @@ set(OPENSHOT_SOURCES
9595
ZmqLogger.cpp
9696
)
9797

98+
set(OPENSHOT_WAYLAND_CAPTURE FALSE)
99+
if(ENABLE_WAYLAND_CAPTURE AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
100+
find_package(PkgConfig)
101+
if(PkgConfig_FOUND)
102+
pkg_check_modules(PC_PIPEWIRE QUIET IMPORTED_TARGET libpipewire-0.3)
103+
pkg_check_modules(PC_LIBSPA QUIET IMPORTED_TARGET libspa-0.2)
104+
pkg_check_modules(PC_GIO QUIET IMPORTED_TARGET gio-2.0)
105+
pkg_check_modules(PC_GIO_UNIX QUIET IMPORTED_TARGET gio-unix-2.0)
106+
if(PC_PIPEWIRE_FOUND AND PC_LIBSPA_FOUND AND PC_GIO_FOUND AND PC_GIO_UNIX_FOUND)
107+
set(OPENSHOT_WAYLAND_CAPTURE TRUE)
108+
list(APPEND OPENSHOT_SOURCES WaylandScreenCaptureReader.cpp)
109+
endif()
110+
endif()
111+
endif()
112+
98113
# OpenCV related classes
99114
set(OPENSHOT_CV_SOURCES
100115
CVTracker.cpp
@@ -195,6 +210,16 @@ target_include_directories(openshot
195210
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
196211
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/libopenshot>)
197212

213+
if(OPENSHOT_WAYLAND_CAPTURE)
214+
target_compile_definitions(openshot PRIVATE HAVE_WAYLAND_CAPTURE=1)
215+
target_link_libraries(openshot PRIVATE
216+
PkgConfig::PC_PIPEWIRE
217+
PkgConfig::PC_LIBSPA
218+
PkgConfig::PC_GIO
219+
PkgConfig::PC_GIO_UNIX)
220+
endif()
221+
add_feature_info("Wayland screen capture" OPENSHOT_WAYLAND_CAPTURE "Use xdg-desktop-portal ScreenCast and PipeWire")
222+
198223
################# LIBOPENSHOT-AUDIO ###################
199224
# Find JUCE-based openshot Audio libraries
200225
if(NOT TARGET OpenShot::Audio)

src/ScreenCaptureReader.cpp

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,15 @@ namespace
4141
}
4242
}
4343

44+
#if defined(HAVE_WAYLAND_CAPTURE)
45+
std::unique_ptr<ScreenCaptureReader::CaptureBackendReader> CreateWaylandScreenCaptureReader(
46+
const ScreenCaptureSettings& settings,
47+
ReaderInfo& info);
48+
#endif
49+
4450
ScreenCaptureReader::ScreenCaptureReader(const ScreenCaptureSettings& new_settings)
4551
: settings(new_settings)
52+
, backend_reader(nullptr)
4653
, is_open(false)
4754
, video_stream(-1)
4855
, frames_read(0)
@@ -59,17 +66,40 @@ ScreenCaptureReader::ScreenCaptureReader(const ScreenCaptureSettings& new_settin
5966
}
6067
ValidateSettings();
6168
PopulateInfo();
69+
if (UsesWaylandPortal()) {
70+
#if defined(HAVE_WAYLAND_CAPTURE)
71+
backend_reader = CreateWaylandScreenCaptureReader(settings, info);
72+
if (!backend_reader) {
73+
throw InvalidOptions("Wayland screen capture backend is unavailable in this build.");
74+
}
75+
#else
76+
throw InvalidOptions("Wayland screen capture backend is unavailable in this build.");
77+
#endif
78+
}
6279
}
6380

6481
ScreenCaptureReader::~ScreenCaptureReader()
6582
{
6683
Close();
6784
}
6885

86+
bool ScreenCaptureReader::IsOpen()
87+
{
88+
return backend_reader ? backend_reader->IsOpen() : is_open;
89+
}
90+
6991
bool ScreenCaptureReader::IsBackendSupported(ScreenCaptureBackend backend)
7092
{
7193
#if defined(__linux__)
72-
return backend == SCREEN_CAPTURE_X11 || backend == SCREEN_CAPTURE_AUTO;
94+
if (backend == SCREEN_CAPTURE_X11 || backend == SCREEN_CAPTURE_AUTO) {
95+
return true;
96+
}
97+
#if defined(HAVE_WAYLAND_CAPTURE)
98+
if (backend == SCREEN_CAPTURE_WAYLAND) {
99+
return true;
100+
}
101+
#endif
102+
return false;
73103
#else
74104
(void) backend;
75105
return false;
@@ -80,6 +110,9 @@ ScreenCaptureBackend ScreenCaptureReader::DefaultBackend()
80110
{
81111
#if defined(__linux__)
82112
const char* session = std::getenv("XDG_SESSION_TYPE");
113+
if (session && std::string(session) == "wayland" && IsBackendSupported(SCREEN_CAPTURE_WAYLAND)) {
114+
return SCREEN_CAPTURE_WAYLAND;
115+
}
83116
if (!session || std::string(session) == "x11") {
84117
return SCREEN_CAPTURE_X11;
85118
}
@@ -93,7 +126,7 @@ void ScreenCaptureReader::ValidateSettings() const
93126
if (!IsBackendSupported(settings.backend)) {
94127
throw InvalidOptions("Screen capture backend is not supported on this OS or session.");
95128
}
96-
if (settings.backend != SCREEN_CAPTURE_X11 && !using_v4l2_device) {
129+
if (!UsesFFmpegDevice() && !UsesWaylandPortal() && !using_v4l2_device) {
97130
throw InvalidOptions("Only the X11 screen capture backend is implemented in this build.");
98131
}
99132
if (settings.width <= 0 || settings.height <= 0) {
@@ -104,6 +137,17 @@ void ScreenCaptureReader::ValidateSettings() const
104137
}
105138
}
106139

140+
bool ScreenCaptureReader::UsesFFmpegDevice() const
141+
{
142+
const bool using_v4l2_device = settings.options.count("input_format_name") && settings.options.at("input_format_name") == "v4l2";
143+
return settings.backend == SCREEN_CAPTURE_X11 || using_v4l2_device;
144+
}
145+
146+
bool ScreenCaptureReader::UsesWaylandPortal() const
147+
{
148+
return settings.backend == SCREEN_CAPTURE_WAYLAND;
149+
}
150+
107151
std::string ScreenCaptureReader::InputFormatName() const
108152
{
109153
const auto override_format = settings.options.find("input_format_name");
@@ -168,6 +212,10 @@ void ScreenCaptureReader::PopulateInfo()
168212

169213
void ScreenCaptureReader::Open()
170214
{
215+
if (backend_reader) {
216+
backend_reader->Open();
217+
return;
218+
}
171219
if (is_open) {
172220
return;
173221
}
@@ -260,6 +308,13 @@ void ScreenCaptureReader::OpenDecoder()
260308

261309
std::shared_ptr<Frame> ScreenCaptureReader::GetFrame(int64_t number)
262310
{
311+
if (backend_reader) {
312+
if (!backend_reader->IsOpen()) {
313+
throw ReaderClosed("The ScreenCaptureReader is closed. Call Open() before GetFrame().");
314+
}
315+
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
316+
return backend_reader->GetFrame(number);
317+
}
263318
if (!is_open) {
264319
throw ReaderClosed("The ScreenCaptureReader is closed. Call Open() before GetFrame().");
265320
}
@@ -337,6 +392,9 @@ std::shared_ptr<Frame> ScreenCaptureReader::DecodeNextFrame(int64_t number)
337392

338393
void ScreenCaptureReader::Close()
339394
{
395+
if (backend_reader) {
396+
backend_reader->Close();
397+
}
340398
if (packet) {
341399
av_packet_free(&packet);
342400
}
@@ -362,6 +420,9 @@ void ScreenCaptureReader::Close()
362420

363421
openshot::CaptureReaderStats ScreenCaptureReader::GetStats() const
364422
{
423+
if (backend_reader) {
424+
return backend_reader->GetStats();
425+
}
365426
CaptureReaderStats stats;
366427
stats.is_open = is_open;
367428
stats.frames_read = frames_read;

src/ScreenCaptureReader.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,26 @@ namespace openshot
5757
class ScreenCaptureReader : public ReaderBase
5858
{
5959
public:
60+
#ifndef SWIG
61+
class CaptureBackendReader
62+
{
63+
public:
64+
virtual ~CaptureBackendReader() = default;
65+
virtual void Open() = 0;
66+
virtual void Close() = 0;
67+
virtual bool IsOpen() const = 0;
68+
virtual std::shared_ptr<openshot::Frame> GetFrame(int64_t number) = 0;
69+
virtual openshot::CaptureReaderStats GetStats() const = 0;
70+
};
71+
#endif
72+
6073
explicit ScreenCaptureReader(const ScreenCaptureSettings& settings);
6174
~ScreenCaptureReader() override;
6275

6376
void Close() override;
6477
CacheBase* GetCache() override { return NULL; };
6578
std::shared_ptr<openshot::Frame> GetFrame(int64_t number) override;
66-
bool IsOpen() override { return is_open; };
79+
bool IsOpen() override;
6780
std::string Name() override { return "ScreenCaptureReader"; };
6881
std::string Json() const override;
6982
void SetJson(const std::string value) override;
@@ -83,9 +96,14 @@ namespace openshot
8396
std::string InputFormatName() const;
8497
std::string InputName() const;
8598
std::shared_ptr<openshot::Frame> DecodeNextFrame(int64_t number);
99+
bool UsesFFmpegDevice() const;
100+
bool UsesWaylandPortal() const;
86101
void PopulateInfo();
87102

88103
ScreenCaptureSettings settings;
104+
#ifndef SWIG
105+
std::unique_ptr<CaptureBackendReader> backend_reader;
106+
#endif
89107
bool is_open;
90108
int video_stream;
91109
int64_t frames_read;

0 commit comments

Comments
 (0)