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.
5 #include "chrome/browser/media/desktop_media_list_ash.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"
24 using content::BrowserThread
;
25 using content::DesktopMediaID
;
29 // Update the list twice per second.
30 const int kDefaultUpdatePeriod
= 500;
34 DesktopMediaListAsh::SourceDescription::SourceDescription(
36 const base::string16
& name
)
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_(content::DesktopMediaID::TYPE_NONE
, -1),
47 pending_window_capture_requests_(0),
51 DesktopMediaListAsh::~DesktopMediaListAsh() {}
53 void DesktopMediaListAsh::SetUpdatePeriod(base::TimeDelta period
) {
55 update_period_
= period
;
58 void DesktopMediaListAsh::SetThumbnailSize(
59 const gfx::Size
& thumbnail_size
) {
60 thumbnail_size_
= thumbnail_size
;
63 void DesktopMediaListAsh::SetViewDialogWindowId(
64 content::DesktopMediaID dialog_id
) {
65 view_dialog_id_
= dialog_id
;
68 void DesktopMediaListAsh::StartUpdating(DesktopMediaListObserver
* observer
) {
75 int DesktopMediaListAsh::GetSourceCount() const {
76 return sources_
.size();
79 const DesktopMediaList::Source
& DesktopMediaListAsh::GetSource(
81 return sources_
[index
];
84 void DesktopMediaListAsh::Refresh() {
85 std::vector
<SourceDescription
> new_sources
;
86 EnumerateSources(&new_sources
);
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
);
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
);
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
);
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
);
117 DCHECK_EQ(new_sources
.size(), sources_
.size());
119 // Find the moved/changed sources.
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
)
130 DCHECK(sources_
[old_pos
].id
== new_sources
[pos
].id
);
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
);
137 observer_
->OnSourceMoved(old_pos
, pos
);
140 if (sources_
[pos
].name
!= new_sources
[pos
].name
) {
141 sources_
[pos
].name
= new_sources
[pos
].name
;
142 observer_
->OnSourceNameChanged(pos
);
148 void DesktopMediaListAsh::EnumerateWindowsForRoot(
149 std::vector
<DesktopMediaListAsh::SourceDescription
>* sources
,
150 aura::Window
* root_window
,
152 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
154 aura::Window
* container
= ash::Shell::GetContainer(root_window
, container_id
);
157 for (aura::Window::Windows::const_iterator it
= container
->children().begin();
158 it
!= container
->children().end(); ++it
) {
159 if (!(*it
)->IsVisible() || !(*it
)->CanFocus())
161 content::DesktopMediaID id
= content::DesktopMediaID::RegisterAuraWindow(
162 content::DesktopMediaID::TYPE_WINDOW
, *it
);
163 if (id
.aura_id
== view_dialog_id_
.aura_id
)
165 SourceDescription
window_source(id
, (*it
)->title());
166 sources
->push_back(window_source
);
168 CaptureThumbnail(window_source
.id
, *it
);
172 void DesktopMediaListAsh::EnumerateSources(
173 std::vector
<DesktopMediaListAsh::SourceDescription
>* sources
) {
174 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
176 aura::Window::Windows root_windows
= ash::Shell::GetAllRootWindows();
178 for (size_t i
= 0; i
< root_windows
.size(); ++i
) {
179 if (source_types_
& SCREENS
) {
180 SourceDescription
screen_source(
181 content::DesktopMediaID::RegisterAuraWindow(
182 content::DesktopMediaID::TYPE_SCREEN
, root_windows
[i
]),
183 root_windows
[i
]->title());
185 if (root_windows
[i
] == ash::Shell::GetPrimaryRootWindow())
186 sources
->insert(sources
->begin(), screen_source
);
188 sources
->push_back(screen_source
);
190 if (screen_source
.name
.empty()) {
191 if (root_windows
.size() > 1) {
192 screen_source
.name
= l10n_util::GetStringFUTF16Int(
193 IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME
,
194 static_cast<int>(i
+ 1));
196 screen_source
.name
= l10n_util::GetStringUTF16(
197 IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME
);
201 CaptureThumbnail(screen_source
.id
, root_windows
[i
]);
204 if (source_types_
& WINDOWS
) {
205 EnumerateWindowsForRoot(
206 sources
, root_windows
[i
], ash::kShellWindowId_DefaultContainer
);
207 EnumerateWindowsForRoot(
208 sources
, root_windows
[i
], ash::kShellWindowId_AlwaysOnTopContainer
);
209 EnumerateWindowsForRoot(
210 sources
, root_windows
[i
], ash::kShellWindowId_DockedContainer
);
215 void DesktopMediaListAsh::CaptureThumbnail(content::DesktopMediaID id
,
216 aura::Window
* window
) {
217 gfx::Rect
window_rect(window
->bounds().width(), window
->bounds().height());
218 gfx::Rect scaled_rect
= media::ComputeLetterboxRegion(
219 gfx::Rect(thumbnail_size_
), window_rect
.size());
221 ++pending_window_capture_requests_
;
222 ui::GrabWindowSnapshotAndScaleAsync(
226 BrowserThread::GetBlockingPool(),
227 base::Bind(&DesktopMediaListAsh::OnThumbnailCaptured
,
228 weak_factory_
.GetWeakPtr(),
232 void DesktopMediaListAsh::OnThumbnailCaptured(content::DesktopMediaID id
,
233 const gfx::Image
& image
) {
234 for (size_t i
= 0; i
< sources_
.size(); ++i
) {
235 if (sources_
[i
].id
== id
) {
236 sources_
[i
].thumbnail
= image
.AsImageSkia();
237 observer_
->OnSourceThumbnailChanged(i
);
242 --pending_window_capture_requests_
;
243 DCHECK_GE(pending_window_capture_requests_
, 0);
245 if (!pending_window_capture_requests_
) {
246 // Once we've finished capturing all windows post a task for the next list
248 BrowserThread::PostDelayedTask(
249 BrowserThread::UI
, FROM_HERE
,
250 base::Bind(&DesktopMediaListAsh::Refresh
,
251 weak_factory_
.GetWeakPtr()),