// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/capture/desktop_capturer_lacros.h"
#include "base/feature_list.h"
#include "chromeos/lacros/lacros_service.h"
#include "content/browser/media/capture/desktop_frame_skia.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
namespace content {
DesktopCapturerLacros::DesktopCapturerLacros(
CaptureType capture_type,
const webrtc::DesktopCaptureOptions& options)
: capture_type_(capture_type), options_(options) {
// Allow this class to be constructed on any sequence.
DETACH_FROM_SEQUENCE(sequence_checker_);
InitializeWidgetMap();
aura::Env::GetInstance()->AddObserver(this);
}
DesktopCapturerLacros::~DesktopCapturerLacros() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
aura::Env::GetInstance()->RemoveObserver(this);
}
bool DesktopCapturerLacros::GetSourceList(SourceList* result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<crosapi::mojom::SnapshotSourcePtr> sources;
{
mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call;
snapshot_capturer_->ListSources(&sources);
}
for (const auto& source : sources) {
Source s;
s.id = source->id;
s.title = source->title;
s.display_id = source->display_id;
if (source->window_unique_id) {
// Use the AcceleratedWidget's value as the in process identifier, since
// that is unique to the process. Since we aren't called on the UI thread,
// we cannot call |DesktopMediaID::RegisterNativeWindow()| directly.
base::AutoLock lock(widget_map_lock_);
if (auto it = widget_map_.find(source->window_unique_id.value());
it != widget_map_.end()) {
s.in_process_id = static_cast<content::DesktopMediaID::Id>(it->second);
}
}
result->push_back(s);
}
return true;
}
bool DesktopCapturerLacros::SelectSource(SourceId id) {
#if DCHECK_IS_ON()
// OK to modify on any thread prior to calling Start.
DCHECK(!callback_ || sequence_checker_.CalledOnValidSequence());
#endif
selected_source_ = id;
return true;
}
bool DesktopCapturerLacros::FocusOnSelectedSource() {
return true;
}
void DesktopCapturerLacros::Start(Callback* callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
callback_ = callback;
// The lacros service exists at all times except during early start-up and
// late shut-down. This class should never be used in those two times.
auto* lacros_service = chromeos::LacrosService::Get();
DCHECK(lacros_service);
// The remote connection to the screen manager. Because we do not live on the
// main thread, we cannot just query for this via |LacrosService::GetRemote|,
// which is thread affine. However, since we only need it to get the Screen
// Capturer, we don't need to keep the remote to it around.
mojo::Remote<crosapi::mojom::ScreenManager> screen_manager;
lacros_service->BindScreenManagerReceiver(
screen_manager.BindNewPipeAndPassReceiver());
// Lacros can assume that Ash is at least M88.
int version =
lacros_service->GetInterfaceVersion<crosapi::mojom::ScreenManager>();
CHECK_GE(version, 1);
if (capture_type_ == kScreen) {
screen_manager->GetScreenCapturer(
snapshot_capturer_.BindNewPipeAndPassReceiver());
} else {
screen_manager->GetWindowCapturer(
snapshot_capturer_.BindNewPipeAndPassReceiver());
}
}
void DesktopCapturerLacros::CaptureFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
DCHECK(!capturing_frame_);
capturing_frame_ = true;
#endif
snapshot_capturer_->TakeSnapshot(
selected_source_, base::BindOnce(&DesktopCapturerLacros::DidTakeSnapshot,
weak_factory_.GetWeakPtr()));
}
bool DesktopCapturerLacros::IsOccluded(const webrtc::DesktopVector& pos) {
return false;
}
void DesktopCapturerLacros::SetSharedMemoryFactory(
std::unique_ptr<webrtc::SharedMemoryFactory> shared_memory_factory) {}
void DesktopCapturerLacros::SetExcludedWindow(webrtc::WindowId window) {}
void DesktopCapturerLacros::DidTakeSnapshot(bool success,
const SkBitmap& snapshot) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
capturing_frame_ = false;
#endif
if (!success) {
callback_->OnCaptureResult(Result::ERROR_PERMANENT,
std::unique_ptr<webrtc::DesktopFrame>());
return;
}
callback_->OnCaptureResult(
Result::SUCCESS, std::make_unique<content::DesktopFrameSkia>(snapshot));
}
void DesktopCapturerLacros::InitializeWidgetMap() {
// On desktop aura there is one WindowTreeHost per top-level window. Thus, by
// iterating through the set of window_tree_hosts, we're essentially iterating
// through the list of known windows.
for (aura::WindowTreeHost* host :
aura::Env::GetInstance()->window_tree_hosts()) {
OnHostInitialized(host);
}
}
void DesktopCapturerLacros::OnHostInitialized(aura::WindowTreeHost* host) {
DCHECK(host);
base::AutoLock lock(widget_map_lock_);
widget_map_.emplace(host->GetUniqueId(), host->GetAcceleratedWidget());
}
void DesktopCapturerLacros::OnHostDestroyed(aura::WindowTreeHost* host) {
DCHECK(host);
// Unfortunately, by the time we get notified that the host is destroyed,
// the platform window has already been destroyed, and so we cannot call
// |WindowTreeHost::GetUniqueId| to remove our widget from the map here.
// As this class is currently only created when the picker is open, we expect
// this notification to be fairly rare, so we'll do this less efficient
// find/erase rather than either maintaining a separate/reverse map or making
// our making the far more common "Find the AcceleratedWidget for the
// corresponding window id from Ash-chrome" use-case slower.
base::AutoLock lock(widget_map_lock_);
auto it = base::ranges::find(widget_map_, host->GetAcceleratedWidget(),
[](auto it) { return it.second; });
if (it != widget_map_.end())
widget_map_.erase(it);
}
} // namespace content