chromium/content/browser/media/capture/desktop_capturer_lacros.cc

// 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