Add plumbing to control PipeWire picker visibility

Introduces the notion of a "delegated source list" and corresponding
controller. This is used by desktop capturers (currently just the
PipeWire capturer), who control selecting the source through their own
(often system-level) UI, rather than returning a source list with all
available options that can then be selected by the embedder.

Adds a method to get the controller which serves to also tell embedders
if the capturer makes use of a delegated source list. The controller
currently allows the embedder to request that the delegated source list
be shown or hidden, and will in the future be used to expose events
from the source list (e.g. selection, dismissal, error).

Bug: chromium:1351572
Change-Id: Ie1d36ed654013f59b8d9095deef01a4705fd5bde
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/272621
Reviewed-by: Mark Foltz <mfoltz@chromium.org>
Commit-Queue: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/main@{#37956}
This commit is contained in:
Alex Cooper
2022-08-30 10:01:18 -07:00
committed by WebRTC LUCI CQ
parent a9d9820928
commit fbea8c5196
10 changed files with 156 additions and 25 deletions

View File

@ -29,6 +29,11 @@ namespace webrtc {
DesktopCapturer::~DesktopCapturer() = default;
DelegatedSourceListController*
DesktopCapturer::GetDelegatedSourceListController() {
return nullptr;
}
void DesktopCapturer::SetSharedMemoryFactory(
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {}

View File

@ -32,6 +32,31 @@ namespace webrtc {
class DesktopCaptureOptions;
class DesktopFrame;
// A controller to be implemented and returned by
// GetDelegatedSourceListController in capturers that require showing their own
// source list and managing user selection there. Apart from ensuring the
// visibility of the source list, these capturers should largely be interacted
// with the same as a normal capturer, though there may be some caveats for
// some DesktopCapturer methods. See GetDelegatedSourceListController for more
// information.
class RTC_EXPORT DelegatedSourceListController {
public:
// Used to prompt the capturer to show the delegated source list. If the
// source list is already visible, this will be a no-op. Must be called after
// starting the DesktopCapturer.
//
// Note that any selection from a previous invocation of the source list may
// be cleared when this method is called.
virtual void EnsureVisible() = 0;
// Used to prompt the capturer to hide the delegated source list. If the
// source list is already hidden, this will be a no-op.
virtual void EnsureHidden() = 0;
protected:
virtual ~DelegatedSourceListController() {}
};
// Abstract interface for screen and window capturers.
class RTC_EXPORT DesktopCapturer {
public:
@ -88,6 +113,18 @@ class RTC_EXPORT DesktopCapturer {
// valid until capturer is destroyed.
virtual void Start(Callback* callback) = 0;
// Returns a valid pointer if the capturer requires the user to make a
// selection from a source list provided by the capturer.
// Returns nullptr if the capturer does not provide a UI for the user to make
// a selection.
//
// Callers should not take ownership of the returned pointer, but it is
// guaranteed to be valid as long as the desktop_capturer is valid.
// Note that consumers should still use GetSourceList and SelectSource, but
// their behavior may be modified if this returns a value. See those methods
// for a more in-depth discussion of those potential modifications.
virtual DelegatedSourceListController* GetDelegatedSourceListController();
// Sets SharedMemoryFactory that will be used to create buffers for the
// captured frames. The factory can be invoked on a thread other than the one
// where CaptureFrame() is called. It will be destroyed on the same thread.
@ -116,10 +153,19 @@ class RTC_EXPORT DesktopCapturer {
// should return monitors.
// For DesktopCapturer implementations to capture windows, this function
// should only return root windows owned by applications.
//
// Note that capturers who use a delegated source list will return a
// SourceList with exactly one value, but it may not be viable for capture
// (e.g. CaptureFrame will return ERROR_TEMPORARY) until a selection has been
// made.
virtual bool GetSourceList(SourceList* sources);
// Selects a source to be captured. Returns false in case of a failure (e.g.
// if there is no source with the specified type and id.)
//
// Note that some capturers with delegated source lists may also support
// selecting a SourceID that is not in the returned source list as a form of
// restore token.
virtual bool SelectSource(SourceId id);
// Brings the selected source to the front and sets the input focus on it.

View File

@ -16,8 +16,6 @@
#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/random.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
@ -44,8 +42,7 @@ BaseCapturerPipeWire::BaseCapturerPipeWire(
: options_(options),
is_screencast_portal_(false),
portal_(std::move(portal)) {
Random random(rtc::TimeMicros());
source_id_ = static_cast<SourceId>(random.Rand(1, INT_MAX));
source_id_ = RestoreTokenManager::GetInstance().GetUnusedId();
}
BaseCapturerPipeWire::~BaseCapturerPipeWire() {}
@ -53,6 +50,11 @@ BaseCapturerPipeWire::~BaseCapturerPipeWire() {}
void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result,
uint32_t stream_node_id,
int fd) {
is_portal_open_ = false;
// Reset the value of capturer_failed_ in case we succeed below. If we fail,
// then it'll set it to the right value again soon enough.
capturer_failed_ = false;
if (result != RequestResponse::kSuccess ||
!options_.screencast_stream()->StartScreenCastStream(
stream_node_id, fd, options_.get_width(), options_.get_height())) {
@ -95,11 +97,15 @@ void BaseCapturerPipeWire::Start(Callback* callback) {
}
}
is_portal_open_ = true;
portal_->Start();
}
void BaseCapturerPipeWire::CaptureFrame() {
if (capturer_failed_) {
// This could be recoverable if the source list is re-summoned; but for our
// purposes this is fine, since it requires intervention to resolve and
// essentially starts a new capture.
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
return;
}
@ -137,6 +143,35 @@ bool BaseCapturerPipeWire::SelectSource(SourceId id) {
return true;
}
DelegatedSourceListController*
BaseCapturerPipeWire::GetDelegatedSourceListController() {
return this;
}
void BaseCapturerPipeWire::EnsureVisible() {
RTC_DCHECK(callback_);
if (is_portal_open_)
return;
// Clear any previously selected state/capture
portal_->Cleanup();
options_.screencast_stream()->StopScreenCastStream();
// Get a new source id to reflect that the source has changed.
source_id_ = RestoreTokenManager::GetInstance().GetUnusedId();
is_portal_open_ = true;
portal_->Start();
}
void BaseCapturerPipeWire::EnsureHidden() {
if (!is_portal_open_)
return;
is_portal_open_ = false;
portal_->Cleanup();
}
SessionDetails BaseCapturerPipeWire::GetSessionDetails() {
return portal_->GetSessionDetails();
}

View File

@ -23,6 +23,7 @@
namespace webrtc {
class BaseCapturerPipeWire : public DesktopCapturer,
public DelegatedSourceListController,
public ScreenCastPortal::PortalNotifier {
public:
explicit BaseCapturerPipeWire(const DesktopCaptureOptions& options);
@ -39,6 +40,11 @@ class BaseCapturerPipeWire : public DesktopCapturer,
void CaptureFrame() override;
bool GetSourceList(SourceList* sources) override;
bool SelectSource(SourceId id) override;
DelegatedSourceListController* GetDelegatedSourceListController() override;
// DelegatedSourceListController
void EnsureVisible() override;
void EnsureHidden() override;
// ScreenCastPortal::PortalNotifier interface.
void OnScreenCastRequestResult(xdg_portal::RequestResponse result,
@ -56,6 +62,7 @@ class BaseCapturerPipeWire : public DesktopCapturer,
Callback* callback_ = nullptr;
bool capturer_failed_ = false;
bool is_screencast_portal_ = false;
bool is_portal_open_ = false;
// SourceId that is selected using SelectSource() and that we previously
// returned in GetSourceList(). This should be a SourceId that has a restore

View File

@ -30,4 +30,8 @@ std::string RestoreTokenManager::TakeToken(DesktopCapturer::SourceId id) {
return token;
}
DesktopCapturer::SourceId RestoreTokenManager::GetUnusedId() {
return ++last_source_id_;
}
} // namespace webrtc

View File

@ -29,10 +29,15 @@ class RestoreTokenManager {
void AddToken(DesktopCapturer::SourceId id, const std::string& token);
std::string TakeToken(DesktopCapturer::SourceId id);
// Returns a source ID which does not have any token associated with it yet.
DesktopCapturer::SourceId GetUnusedId();
private:
RestoreTokenManager() = default;
~RestoreTokenManager() = default;
DesktopCapturer::SourceId last_source_id_ = 0;
std::unordered_map<DesktopCapturer::SourceId, std::string> restore_tokens_;
};

View File

@ -42,6 +42,9 @@ class ScreenCapturePortalInterface {
virtual xdg_portal::SessionDetails GetSessionDetails() { return {}; }
// Starts the portal setup.
virtual void Start() {}
virtual void Cleanup() {}
// Notifies observers about the success/fail state of the portal
// request/response.
virtual void OnPortalDone(xdg_portal::RequestResponse result) {}

View File

@ -66,9 +66,11 @@ void ScreenCastPortal::Cleanup() {
session_handle_ = "";
cancellable_ = nullptr;
proxy_ = nullptr;
restore_token_ = "";
if (pw_fd_ != -1) {
close(pw_fd_);
pw_fd_ = -1;
}
}

View File

@ -112,7 +112,7 @@ class ScreenCastPortal : public xdg_portal::ScreenCapturePortalInterface {
// Sends a create session request to the portal.
void RequestSession(GDBusProxy* proxy) override;
void Cleanup();
void Cleanup() override;
// Set of methods leveraged by remote desktop portal to setup a common session
// with screen cast portal.

View File

@ -98,6 +98,9 @@ class SharedScreenCastStreamPrivate {
DesktopVector CaptureCursorPosition();
private:
// Stops the streams and cleans up any in-use elements.
void StopAndCleanupStream();
uint32_t pw_stream_node_id_ = 0;
DesktopSize stream_size_ = {};
@ -362,25 +365,7 @@ void SharedScreenCastStreamPrivate::OnRenegotiateFormat(void* data, uint64_t) {
SharedScreenCastStreamPrivate::SharedScreenCastStreamPrivate() {}
SharedScreenCastStreamPrivate::~SharedScreenCastStreamPrivate() {
if (pw_main_loop_) {
pw_thread_loop_stop(pw_main_loop_);
}
if (pw_stream_) {
pw_stream_destroy(pw_stream_);
}
if (pw_core_) {
pw_core_disconnect(pw_core_);
}
if (pw_context_) {
pw_context_destroy(pw_context_);
}
if (pw_main_loop_) {
pw_thread_loop_destroy(pw_main_loop_);
}
StopAndCleanupStream();
}
RTC_NO_SANITIZE("cfi-icall")
@ -549,15 +534,54 @@ void SharedScreenCastStreamPrivate::UpdateScreenCastStreamResolution(
}
void SharedScreenCastStreamPrivate::StopScreenCastStream() {
StopAndCleanupStream();
}
void SharedScreenCastStreamPrivate::StopAndCleanupStream() {
// We get buffers on the PipeWire thread, but this is called from the capturer
// thread, so we need to wait on and stop the pipewire thread before we
// disconnect the stream so that we can guarantee we aren't in the middle of
// processing a new frame.
// Even if we *do* somehow have the other objects without a pipewire thread,
// destroying them without a thread causes a crash.
if (!pw_main_loop_)
return;
// While we can stop the thread now, we cannot destroy it until we've cleaned
// up the other members.
pw_thread_loop_wait(pw_main_loop_);
pw_thread_loop_stop(pw_main_loop_);
if (pw_stream_) {
pw_stream_disconnect(pw_stream_);
pw_stream_destroy(pw_stream_);
pw_stream_ = nullptr;
{
webrtc::MutexLock lock(&queue_lock_);
queue_.Reset();
}
}
if (pw_core_) {
pw_core_disconnect(pw_core_);
pw_core_ = nullptr;
}
if (pw_context_) {
pw_context_destroy(pw_context_);
pw_context_ = nullptr;
}
pw_thread_loop_destroy(pw_main_loop_);
pw_main_loop_ = nullptr;
}
std::unique_ptr<DesktopFrame> SharedScreenCastStreamPrivate::CaptureFrame() {
webrtc::MutexLock lock(&queue_lock_);
if (!queue_.current_frame()) {
if (!pw_stream_ || !queue_.current_frame()) {
return std::unique_ptr<DesktopFrame>{};
}