1 /*
2 * Copyright 2018 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "modules/desktop_capture/linux/wayland/base_capturer_pipewire.h"
12
13 #include "modules/desktop_capture/desktop_capture_options.h"
14 #include "modules/desktop_capture/desktop_capturer.h"
15 #include "modules/desktop_capture/linux/wayland/restore_token_manager.h"
16 #include "modules/portal/pipewire_utils.h"
17 #include "modules/portal/xdg_desktop_portal_utils.h"
18 #include "rtc_base/checks.h"
19 #include "rtc_base/logging.h"
20
21 namespace webrtc {
22
23 namespace {
24
25 using xdg_portal::RequestResponse;
26 using xdg_portal::ScreenCapturePortalInterface;
27 using xdg_portal::SessionDetails;
28
29 } // namespace
30
31 // static
IsSupported()32 bool BaseCapturerPipeWire::IsSupported() {
33 // Unfortunately, the best way we have to check if PipeWire is available is
34 // to try to initialize it.
35 // InitializePipeWire should prevent us from repeatedly initializing PipeWire,
36 // but we also don't really expect support to change without the application
37 // restarting.
38 static bool supported =
39 DesktopCapturer::IsRunningUnderWayland() && InitializePipeWire();
40 return supported;
41 }
42
BaseCapturerPipeWire(const DesktopCaptureOptions & options,CaptureType type)43 BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options,
44 CaptureType type)
45 : BaseCapturerPipeWire(options,
46 std::make_unique<ScreenCastPortal>(type, this)) {
47 is_screencast_portal_ = true;
48 }
49
BaseCapturerPipeWire(const DesktopCaptureOptions & options,std::unique_ptr<ScreenCapturePortalInterface> portal)50 BaseCapturerPipeWire::BaseCapturerPipeWire(
51 const DesktopCaptureOptions& options,
52 std::unique_ptr<ScreenCapturePortalInterface> portal)
53 : options_(options),
54 is_screencast_portal_(false),
55 portal_(std::move(portal)) {
56 source_id_ = RestoreTokenManager::GetInstance().GetUnusedId();
57 options_.screencast_stream()->SetUseDamageRegion(
58 options_.pipewire_use_damage_region());
59 }
60
~BaseCapturerPipeWire()61 BaseCapturerPipeWire::~BaseCapturerPipeWire() {
62 options_.screencast_stream()->StopScreenCastStream();
63 }
64
OnScreenCastRequestResult(RequestResponse result,uint32_t stream_node_id,int fd)65 void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result,
66 uint32_t stream_node_id,
67 int fd) {
68 is_portal_open_ = false;
69
70 // Reset the value of capturer_failed_ in case we succeed below. If we fail,
71 // then it'll set it to the right value again soon enough.
72 capturer_failed_ = false;
73 if (result != RequestResponse::kSuccess ||
74 !options_.screencast_stream()->StartScreenCastStream(
75 stream_node_id, fd, options_.get_width(), options_.get_height(),
76 options_.prefer_cursor_embedded())) {
77 capturer_failed_ = true;
78 RTC_LOG(LS_ERROR) << "ScreenCastPortal failed: "
79 << static_cast<uint>(result);
80 } else if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
81 if (!screencast_portal->RestoreToken().empty()) {
82 RestoreTokenManager::GetInstance().AddToken(
83 source_id_, screencast_portal->RestoreToken());
84 }
85 }
86
87 if (!delegated_source_list_observer_)
88 return;
89
90 switch (result) {
91 case RequestResponse::kUnknown:
92 RTC_DCHECK_NOTREACHED();
93 break;
94 case RequestResponse::kSuccess:
95 delegated_source_list_observer_->OnSelection();
96 break;
97 case RequestResponse::kUserCancelled:
98 delegated_source_list_observer_->OnCancelled();
99 break;
100 case RequestResponse::kError:
101 delegated_source_list_observer_->OnError();
102 break;
103 }
104 }
105
OnScreenCastSessionClosed()106 void BaseCapturerPipeWire::OnScreenCastSessionClosed() {
107 if (!capturer_failed_) {
108 options_.screencast_stream()->StopScreenCastStream();
109 }
110 }
111
UpdateResolution(uint32_t width,uint32_t height)112 void BaseCapturerPipeWire::UpdateResolution(uint32_t width, uint32_t height) {
113 if (!capturer_failed_) {
114 options_.screencast_stream()->UpdateScreenCastStreamResolution(width,
115 height);
116 }
117 }
118
Start(Callback * callback)119 void BaseCapturerPipeWire::Start(Callback* callback) {
120 RTC_DCHECK(!callback_);
121 RTC_DCHECK(callback);
122
123 callback_ = callback;
124
125 if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
126 screencast_portal->SetPersistMode(
127 ScreenCastPortal::PersistMode::kTransient);
128 if (selected_source_id_) {
129 screencast_portal->SetRestoreToken(
130 RestoreTokenManager::GetInstance().TakeToken(selected_source_id_));
131 }
132 }
133
134 is_portal_open_ = true;
135 portal_->Start();
136 }
137
CaptureFrame()138 void BaseCapturerPipeWire::CaptureFrame() {
139 if (capturer_failed_) {
140 // This could be recoverable if the source list is re-summoned; but for our
141 // purposes this is fine, since it requires intervention to resolve and
142 // essentially starts a new capture.
143 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
144 return;
145 }
146
147 std::unique_ptr<DesktopFrame> frame =
148 options_.screencast_stream()->CaptureFrame();
149
150 if (!frame || !frame->data()) {
151 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
152 return;
153 }
154
155 // TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on
156 // the frame, see ScreenCapturerX11::CaptureFrame.
157
158 frame->set_capturer_id(DesktopCapturerId::kWaylandCapturerLinux);
159 callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
160 }
161
GetSourceList(SourceList * sources)162 bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) {
163 RTC_DCHECK(sources->size() == 0);
164 // List of available screens is already presented by the xdg-desktop-portal,
165 // so we just need a (valid) source id for any callers to pass around, even
166 // though it doesn't mean anything to us. Until the user selects a source in
167 // xdg-desktop-portal we'll just end up returning empty frames. Note that "0"
168 // is often treated as a null/placeholder id, so we shouldn't use that.
169 // TODO(https://crbug.com/1297671): Reconsider type of ID when plumbing
170 // token that will enable stream re-use.
171 sources->push_back({source_id_});
172 return true;
173 }
174
SelectSource(SourceId id)175 bool BaseCapturerPipeWire::SelectSource(SourceId id) {
176 // Screen selection is handled by the xdg-desktop-portal.
177 selected_source_id_ = id;
178 return true;
179 }
180
181 DelegatedSourceListController*
GetDelegatedSourceListController()182 BaseCapturerPipeWire::GetDelegatedSourceListController() {
183 return this;
184 }
185
Observe(Observer * observer)186 void BaseCapturerPipeWire::Observe(Observer* observer) {
187 RTC_DCHECK(!delegated_source_list_observer_ || !observer);
188 delegated_source_list_observer_ = observer;
189 }
190
EnsureVisible()191 void BaseCapturerPipeWire::EnsureVisible() {
192 RTC_DCHECK(callback_);
193 if (is_portal_open_)
194 return;
195
196 // Clear any previously selected state/capture
197 portal_->Stop();
198 options_.screencast_stream()->StopScreenCastStream();
199
200 // Get a new source id to reflect that the source has changed.
201 source_id_ = RestoreTokenManager::GetInstance().GetUnusedId();
202
203 is_portal_open_ = true;
204 portal_->Start();
205 }
206
EnsureHidden()207 void BaseCapturerPipeWire::EnsureHidden() {
208 if (!is_portal_open_)
209 return;
210
211 is_portal_open_ = false;
212 portal_->Stop();
213 }
214
GetSessionDetails()215 SessionDetails BaseCapturerPipeWire::GetSessionDetails() {
216 return portal_->GetSessionDetails();
217 }
218
GetScreenCastPortal()219 ScreenCastPortal* BaseCapturerPipeWire::GetScreenCastPortal() {
220 return is_screencast_portal_ ? static_cast<ScreenCastPortal*>(portal_.get())
221 : nullptr;
222 }
223
224 } // namespace webrtc
225