chromium/chrome/browser/media/webrtc/desktop_media_list_ash.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/media/webrtc/desktop_media_list_ash.h"

#include <utility>

#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/window_properties.h"
#include "base/containers/adapters.h"
#include "base/functional/bind.h"
#include "chrome/grit/generated_resources.h"
#include "media/base/video_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image/image.h"
#include "ui/snapshot/snapshot.h"

using content::DesktopMediaID;

namespace {

// Update the list twice per second.
const int kDefaultDesktopMediaListUpdatePeriod = 500;

}  // namespace

DesktopMediaListAsh::DesktopMediaListAsh(DesktopMediaList::Type type)
    : DesktopMediaListBase(
          base::Milliseconds(kDefaultDesktopMediaListUpdatePeriod)) {
  DCHECK(type == DesktopMediaList::Type::kScreen ||
         type == DesktopMediaList::Type::kWindow);
  type_ = type;
}

DesktopMediaListAsh::~DesktopMediaListAsh() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void DesktopMediaListAsh::Refresh(bool update_thumbnails) {
  DCHECK(can_refresh());
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(pending_window_capture_requests_, 0);

  std::vector<SourceDescription> new_sources;
  EnumerateSources(&new_sources, update_thumbnails);
  UpdateSourcesList(new_sources);
  OnRefreshMaybeComplete();
}

void DesktopMediaListAsh::EnumerateWindowsForRoot(
    std::vector<DesktopMediaListAsh::SourceDescription>* sources,
    bool update_thumbnails,
    aura::Window* root_window,
    int container_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  aura::Window* container = ash::Shell::GetContainer(root_window, container_id);
  if (!container) {
    return;
  }

  // The |container| has all the top-level windows in reverse order, e.g. the
  // most top-level window is at the end. So iterate children reversely to make
  // sure |sources| is in the expected order.
  for (aura::Window* window : base::Reversed(container->children())) {
    if (!window->IsVisible() || !window->CanFocus() ||
        window->GetProperty(ash::kOverviewUiKey) ||
        window->GetProperty(ash::kExcludeInMruKey)) {
      continue;
    }

    content::DesktopMediaID id = content::DesktopMediaID::RegisterNativeWindow(
        content::DesktopMediaID::TYPE_WINDOW, window);
    if (id.window_id == view_dialog_id_.window_id) {
      continue;
    }

    SourceDescription window_source(id, window->GetTitle());
    sources->push_back(window_source);

    if (update_thumbnails) {
      CaptureThumbnail(window_source.id, window);
    }
  }
}

void DesktopMediaListAsh::EnumerateSources(
    std::vector<DesktopMediaListAsh::SourceDescription>* sources,
    bool update_thumbnails) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();

  for (size_t i = 0; i < root_windows.size(); ++i) {
    if (type_ == DesktopMediaList::Type::kScreen) {
      SourceDescription screen_source(
          content::DesktopMediaID::RegisterNativeWindow(
              content::DesktopMediaID::TYPE_SCREEN, root_windows[i]),
          root_windows[i]->GetTitle());

      if (root_windows[i] == ash::Shell::GetPrimaryRootWindow())
        sources->insert(sources->begin(), screen_source);
      else
        sources->push_back(screen_source);

      if (screen_source.name.empty()) {
        if (root_windows.size() > 1) {
          // 'Screen' in 'Screen 1, Screen 2, etc ' might be inflected in some
          // languages depending on the number although rather unlikely. To be
          // safe, use the plural format.
          // TODO(jshin): Revert to GetStringFUTF16Int (with native digits)
          // if none of UI languages inflects 'Screen' in this context.
          screen_source.name = l10n_util::GetPluralStringFUTF16(
              IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME,
              static_cast<int>(i + 1));
        } else {
          screen_source.name = l10n_util::GetStringUTF16(
              IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME);
        }
      }

      if (update_thumbnails)
        CaptureThumbnail(screen_source.id, root_windows[i]);
    } else {
      for (int desk_id : ash::desks_util::GetDesksContainersIds()) {
        EnumerateWindowsForRoot(sources, update_thumbnails, root_windows[i],
                                desk_id);
      }

      EnumerateWindowsForRoot(sources, update_thumbnails, root_windows[i],
                              ash::kShellWindowId_AlwaysOnTopContainer);
      EnumerateWindowsForRoot(sources, update_thumbnails, root_windows[i],
                              ash::kShellWindowId_PipContainer);
      EnumerateWindowsForRoot(sources, update_thumbnails, root_windows[i],
                              ash::kShellWindowId_FloatContainer);
    }
  }
}

void DesktopMediaListAsh::CaptureThumbnail(content::DesktopMediaID id,
                                           aura::Window* window) {
  gfx::Rect window_rect(window->bounds().width(), window->bounds().height());
  gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
      gfx::Rect(thumbnail_size_), window_rect.size());

  ++pending_window_capture_requests_;
  ui::GrabWindowSnapshotAndScale(
      window, window_rect, scaled_rect.size(),
      base::BindOnce(&DesktopMediaListAsh::OnThumbnailCaptured,
                     weak_factory_.GetWeakPtr(), id));
}

void DesktopMediaListAsh::OnThumbnailCaptured(content::DesktopMediaID id,
                                              gfx::Image image) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  UpdateSourceThumbnail(id, image.AsImageSkia());

  --pending_window_capture_requests_;
  DCHECK_GE(pending_window_capture_requests_, 0);

  OnRefreshMaybeComplete();
}

void DesktopMediaListAsh::OnRefreshMaybeComplete() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (pending_window_capture_requests_ == 0) {
    // Once we've finished capturing all windows, notify the caller, which will
    // post a task for the next list update if necessary.
    OnRefreshComplete();
  }
}