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 "content/public/browser/browser_thread.h" 17 #include "grit/generated_resources.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