Home | History | Annotate | Download | only in media
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/media/desktop_media_list_ash.h"
      6 
      7 #include <set>
      8 
      9 #include "ash/shell.h"
     10 #include "ash/shell_window_ids.h"
     11 #include "base/hash.h"
     12 #include "base/logging.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/threading/sequenced_worker_pool.h"
     15 #include "chrome/browser/media/desktop_media_list_observer.h"
     16 #include "chrome/grit/generated_resources.h"
     17 #include "content/public/browser/browser_thread.h"
     18 #include "media/base/video_util.h"
     19 #include "ui/base/l10n/l10n_util.h"
     20 #include "ui/compositor/dip_util.h"
     21 #include "ui/gfx/image/image.h"
     22 #include "ui/snapshot/snapshot.h"
     23 
     24 using content::BrowserThread;
     25 using content::DesktopMediaID;
     26 
     27 namespace {
     28 
     29 // Update the list twice per second.
     30 const int kDefaultUpdatePeriod = 500;
     31 
     32 }  // namespace
     33 
     34 DesktopMediaListAsh::SourceDescription::SourceDescription(
     35     DesktopMediaID id,
     36     const base::string16& name)
     37     : id(id),
     38       name(name) {
     39 }
     40 
     41 DesktopMediaListAsh::DesktopMediaListAsh(int source_types)
     42     : source_types_(source_types),
     43       update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)),
     44       thumbnail_size_(100, 100),
     45       view_dialog_id_(-1),
     46       observer_(NULL),
     47       pending_window_capture_requests_(0),
     48       weak_factory_(this) {
     49 }
     50 
     51 DesktopMediaListAsh::~DesktopMediaListAsh() {}
     52 
     53 void DesktopMediaListAsh::SetUpdatePeriod(base::TimeDelta period) {
     54   DCHECK(!observer_);
     55   update_period_ = period;
     56 }
     57 
     58 void DesktopMediaListAsh::SetThumbnailSize(
     59     const gfx::Size& thumbnail_size) {
     60   thumbnail_size_ = thumbnail_size;
     61 }
     62 
     63 void DesktopMediaListAsh::SetViewDialogWindowId(
     64     content::DesktopMediaID::Id dialog_id) {
     65   view_dialog_id_ = dialog_id;
     66 }
     67 
     68 void DesktopMediaListAsh::StartUpdating(DesktopMediaListObserver* observer) {
     69   DCHECK(!observer_);
     70 
     71   observer_ = observer;
     72   Refresh();
     73 }
     74 
     75 int DesktopMediaListAsh::GetSourceCount() const {
     76   return sources_.size();
     77 }
     78 
     79 const DesktopMediaList::Source& DesktopMediaListAsh::GetSource(
     80     int index) const {
     81   return sources_[index];
     82 }
     83 
     84 void DesktopMediaListAsh::Refresh() {
     85   std::vector<SourceDescription> new_sources;
     86   EnumerateSources(&new_sources);
     87 
     88   typedef std::set<content::DesktopMediaID> SourceSet;
     89   SourceSet new_source_set;
     90   for (size_t i = 0; i < new_sources.size(); ++i) {
     91     new_source_set.insert(new_sources[i].id);
     92   }
     93   // Iterate through the old sources to find the removed sources.
     94   for (size_t i = 0; i < sources_.size(); ++i) {
     95     if (new_source_set.find(sources_[i].id) == new_source_set.end()) {
     96       sources_.erase(sources_.begin() + i);
     97       observer_->OnSourceRemoved(i);
     98       --i;
     99     }
    100   }
    101   // Iterate through the new sources to find the added sources.
    102   if (new_sources.size() > sources_.size()) {
    103     SourceSet old_source_set;
    104     for (size_t i = 0; i < sources_.size(); ++i) {
    105       old_source_set.insert(sources_[i].id);
    106     }
    107 
    108     for (size_t i = 0; i < new_sources.size(); ++i) {
    109       if (old_source_set.find(new_sources[i].id) == old_source_set.end()) {
    110         sources_.insert(sources_.begin() + i, Source());
    111         sources_[i].id = new_sources[i].id;
    112         sources_[i].name = new_sources[i].name;
    113         observer_->OnSourceAdded(i);
    114       }
    115     }
    116   }
    117   DCHECK_EQ(new_sources.size(), sources_.size());
    118 
    119   // Find the moved/changed sources.
    120   size_t pos = 0;
    121   while (pos < sources_.size()) {
    122     if (!(sources_[pos].id == new_sources[pos].id)) {
    123       // Find the source that should be moved to |pos|, starting from |pos + 1|
    124       // of |sources_|, because entries before |pos| should have been sorted.
    125       size_t old_pos = pos + 1;
    126       for (; old_pos < sources_.size(); ++old_pos) {
    127         if (sources_[old_pos].id == new_sources[pos].id)
    128           break;
    129       }
    130       DCHECK(sources_[old_pos].id == new_sources[pos].id);
    131 
    132       // Move the source from |old_pos| to |pos|.
    133       Source temp = sources_[old_pos];
    134       sources_.erase(sources_.begin() + old_pos);
    135       sources_.insert(sources_.begin() + pos, temp);
    136 
    137       observer_->OnSourceMoved(old_pos, pos);
    138     }
    139 
    140     if (sources_[pos].name != new_sources[pos].name) {
    141       sources_[pos].name = new_sources[pos].name;
    142       observer_->OnSourceNameChanged(pos);
    143     }
    144     ++pos;
    145   }
    146 }
    147 
    148 void DesktopMediaListAsh::EnumerateWindowsForRoot(
    149     std::vector<DesktopMediaListAsh::SourceDescription>* sources,
    150     aura::Window* root_window,
    151     int container_id) {
    152   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    153 
    154   aura::Window* container = ash::Shell::GetContainer(root_window, container_id);
    155   if (!container)
    156     return;
    157   for (aura::Window::Windows::const_iterator it = container->children().begin();
    158        it != container->children().end(); ++it) {
    159     if (!(*it)->IsVisible() || !(*it)->CanFocus())
    160       continue;
    161     content::DesktopMediaID id =
    162         content::DesktopMediaID::RegisterAuraWindow(*it);
    163     if (id.id == view_dialog_id_)
    164       continue;
    165     SourceDescription window_source(id, (*it)->title());
    166     sources->push_back(window_source);
    167 
    168     CaptureThumbnail(window_source.id, *it);
    169   }
    170 }
    171 
    172 void DesktopMediaListAsh::EnumerateSources(
    173     std::vector<DesktopMediaListAsh::SourceDescription>* sources) {
    174   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    175 
    176   aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
    177 
    178   for (size_t i = 0; i < root_windows.size(); ++i) {
    179     if (source_types_ & SCREENS) {
    180       SourceDescription screen_source(
    181           content::DesktopMediaID::RegisterAuraWindow(root_windows[i]),
    182           root_windows[i]->title());
    183 
    184       if (root_windows[i] == ash::Shell::GetPrimaryRootWindow())
    185         sources->insert(sources->begin(), screen_source);
    186       else
    187         sources->push_back(screen_source);
    188 
    189       if (screen_source.name.empty()) {
    190         if (root_windows.size() > 1) {
    191           screen_source.name = l10n_util::GetStringFUTF16Int(
    192               IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME,
    193               static_cast<int>(i + 1));
    194         } else {
    195           screen_source.name = l10n_util::GetStringUTF16(
    196               IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME);
    197         }
    198       }
    199 
    200       CaptureThumbnail(screen_source.id, root_windows[i]);
    201     }
    202 
    203     if (source_types_ & WINDOWS) {
    204       EnumerateWindowsForRoot(
    205           sources, root_windows[i], ash::kShellWindowId_DefaultContainer);
    206       EnumerateWindowsForRoot(
    207           sources, root_windows[i], ash::kShellWindowId_AlwaysOnTopContainer);
    208       EnumerateWindowsForRoot(
    209           sources, root_windows[i], ash::kShellWindowId_DockedContainer);
    210     }
    211   }
    212 }
    213 
    214 void DesktopMediaListAsh::CaptureThumbnail(content::DesktopMediaID id,
    215                                            aura::Window* window) {
    216   gfx::Rect window_rect(window->bounds().width(), window->bounds().height());
    217   gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
    218       gfx::Rect(thumbnail_size_), window_rect.size());
    219 
    220   ++pending_window_capture_requests_;
    221   ui::GrabWindowSnapshotAndScaleAsync(
    222       window,
    223       window_rect,
    224       scaled_rect.size(),
    225       BrowserThread::GetBlockingPool(),
    226       base::Bind(&DesktopMediaListAsh::OnThumbnailCaptured,
    227                  weak_factory_.GetWeakPtr(),
    228                  id));
    229 }
    230 
    231 void DesktopMediaListAsh::OnThumbnailCaptured(content::DesktopMediaID id,
    232                                               const gfx::Image& image) {
    233   for (size_t i = 0; i < sources_.size(); ++i) {
    234     if (sources_[i].id == id) {
    235       sources_[i].thumbnail = image.AsImageSkia();
    236       observer_->OnSourceThumbnailChanged(i);
    237       break;
    238     }
    239   }
    240 
    241   --pending_window_capture_requests_;
    242   DCHECK_GE(pending_window_capture_requests_, 0);
    243 
    244   if (!pending_window_capture_requests_) {
    245     // Once we've finished capturing all windows post a task for the next list
    246     // update.
    247     BrowserThread::PostDelayedTask(
    248         BrowserThread::UI, FROM_HERE,
    249         base::Bind(&DesktopMediaListAsh::Refresh,
    250                    weak_factory_.GetWeakPtr()),
    251         update_period_);
    252   }
    253 }
    254