From c9fbde321c5595430fa869774f4bb942470e9221 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Fri, 2 Oct 2015 17:50:10 +0800 Subject: [PATCH] Implement desktop capture API on OS X. --- atom.gyp | 4 + atom/browser/api/atom_api_desktop_capturer.cc | 127 ++++++ atom/browser/api/atom_api_desktop_capturer.h | 51 +++ atom/browser/api/atom_api_screen.cc | 18 +- atom/browser/api/atom_api_screen.h | 5 + atom/browser/api/lib/app.coffee | 14 +- atom/common/node_bindings.cc | 1 + .../chrome/browser/media/desktop_media_list.h | 60 +++ .../media/desktop_media_list_observer.h | 22 ++ .../media/native_desktop_media_list.cc | 366 ++++++++++++++++++ .../browser/media/native_desktop_media_list.h | 100 +++++ filenames.gypi | 6 + 12 files changed, 768 insertions(+), 6 deletions(-) create mode 100644 atom/browser/api/atom_api_desktop_capturer.cc create mode 100644 atom/browser/api/atom_api_desktop_capturer.h create mode 100644 chromium_src/chrome/browser/media/desktop_media_list.h create mode 100644 chromium_src/chrome/browser/media/desktop_media_list_observer.h create mode 100644 chromium_src/chrome/browser/media/native_desktop_media_list.cc create mode 100644 chromium_src/chrome/browser/media/native_desktop_media_list.h diff --git a/atom.gyp b/atom.gyp index 3a46f242a238..ed885743c6a6 100644 --- a/atom.gyp +++ b/atom.gyp @@ -245,6 +245,10 @@ 'vendor/node/deps/cares/include', # The `third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h` is using `platform/PlatformExport.h`. '<(libchromiumcontent_src_dir)/third_party/WebKit/Source', + # The 'third_party/libyuv/include/libyuv/scale_argb.h' is using 'libyuv/basic_types.h'. + '<(libchromiumcontent_src_dir)/third_party/libyuv/include', + # The 'third_party/webrtc/modules/desktop_capture/desktop_frame.h' is using 'webrtc/base/scoped_ptr.h'. + '<(libchromiumcontent_src_dir)/third_party/', ], 'direct_dependent_settings': { 'include_dirs': [ diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc new file mode 100644 index 000000000000..c3a573715703 --- /dev/null +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -0,0 +1,127 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_desktop_capturer.h" + +#include "atom/common/api/atom_api_native_image.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/node_includes.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/media/desktop_media_list.h" +#include "native_mate/dictionary.h" +#include "native_mate/handle.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" +#include "third_party/webrtc/modules/desktop_capture/window_capturer.h" + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const DesktopMediaList::Source& source) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + content::DesktopMediaID id = source.id; + dict.Set("name", base::UTF16ToUTF8(source.name)); + dict.Set("id", id.ToString()); + dict.Set("thumbnail", + atom::api::NativeImage::Create(isolate, gfx::Image(source.thumbnail))); + return ConvertToV8(isolate, dict); + } +}; + +} // namespace mate + +namespace atom { + +namespace api { + +namespace { +// The wrapDesktopCapturer funtion which is implemented in JavaScript +using WrapDesktopCapturerCallback = base::Callback)>; +WrapDesktopCapturerCallback g_wrap_desktop_capturer; + +const int kThumbnailWidth = 150; +const int kThumbnailHeight = 150; +} // namespace + +DesktopCapturer::DesktopCapturer(bool show_screens, bool show_windows) { + scoped_ptr screen_capturer( + show_screens ? webrtc::ScreenCapturer::Create() : nullptr); + scoped_ptr window_capturer( + show_windows ? webrtc::WindowCapturer::Create() : nullptr); + media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), + window_capturer.Pass())); + media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight)); + media_list_->StartUpdating(this); +} + +DesktopCapturer::~DesktopCapturer() { +} + +const DesktopMediaList::Source& DesktopCapturer::GetSource(int index) { + return media_list_->GetSource(index); +} + +void DesktopCapturer::OnSourceAdded(int index) { + Emit("source-added", index); +} + +void DesktopCapturer::OnSourceRemoved(int index) { + Emit("source-removed", index); +} + +void DesktopCapturer::OnSourceMoved(int old_index, int new_index) { + Emit("source-moved", old_index, new_index); +} + +void DesktopCapturer::OnSourceNameChanged(int index) { + Emit("source-name-changed", index); +} + +void DesktopCapturer::OnSourceThumbnailChanged(int index) { + Emit("source-thumbnail-changed", index); +} + +mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return mate::ObjectTemplateBuilder(isolate) + .SetMethod("getSource", &DesktopCapturer::GetSource); +} + +void SetWrapDesktopCapturer(const WrapDesktopCapturerCallback& callback) { + g_wrap_desktop_capturer = callback; +} + +void ClearWrapDesktopCapturer() { + g_wrap_desktop_capturer.Reset(); +} + +// static +mate::Handle DesktopCapturer::Create(v8::Isolate* isolate, + bool show_screens, bool show_windows) { + auto handle = mate::CreateHandle(isolate, + new DesktopCapturer(show_screens, show_windows)); + g_wrap_desktop_capturer.Run(handle.ToV8()); + return handle; +} + +} // namespace api + +} // namespace atom + +namespace { + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.SetMethod("_setWrapDesktopCapturer", &atom::api::SetWrapDesktopCapturer); + dict.SetMethod("_clearWrapDesktopCapturer", + &atom::api::ClearWrapDesktopCapturer); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_desktop_capturer, Initialize); diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h new file mode 100644 index 000000000000..aae0a8268b9d --- /dev/null +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -0,0 +1,51 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_ +#define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_ + +#include "base/memory/scoped_ptr.h" +#include "atom/browser/api/event_emitter.h" +#include "chrome/browser/media/desktop_media_list_observer.h" +#include "chrome/browser/media/native_desktop_media_list.h" +#include "native_mate/handle.h" + +namespace atom { + +namespace api { + +class DesktopCapturer: public mate::EventEmitter, + public DesktopMediaListObserver { + public: + static mate::Handle Create(v8::Isolate* isolate, + bool show_screens, bool show_windows); + + const DesktopMediaList::Source& GetSource(int index); + + protected: + DesktopCapturer(bool show_screens, bool show_windows); + ~DesktopCapturer(); + + // DesktopMediaListObserver overrides. + void OnSourceAdded(int index) override; + void OnSourceRemoved(int index) override; + void OnSourceMoved(int old_index, int new_index) override; + void OnSourceNameChanged(int index) override; + void OnSourceThumbnailChanged(int index) override; + + private: + // mate::Wrappable: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; + + scoped_ptr media_list_; + + DISALLOW_COPY_AND_ASSIGN(DesktopCapturer); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_ diff --git a/atom/browser/api/atom_api_screen.cc b/atom/browser/api/atom_api_screen.cc index b73bda9ced84..761ef28ef97e 100644 --- a/atom/browser/api/atom_api_screen.cc +++ b/atom/browser/api/atom_api_screen.cc @@ -56,6 +56,21 @@ Screen::~Screen() { screen_->RemoveObserver(this); } +mate::Handle Screen::GetDesktopCapturer( + const std::vector& sources) { + bool show_screens = false; + bool show_windows = false; + for (const auto& source_type : sources) { + if (source_type == "screen") { + show_screens = true; + } else if (source_type == "window") { + show_windows = true; + } + } + + return DesktopCapturer::Create(isolate(), show_screens, show_windows); +} + gfx::Point Screen::GetCursorScreenPoint() { return screen_->GetCursorScreenPoint(); } @@ -107,7 +122,8 @@ mate::ObjectTemplateBuilder Screen::GetObjectTemplateBuilder( .SetMethod("getPrimaryDisplay", &Screen::GetPrimaryDisplay) .SetMethod("getAllDisplays", &Screen::GetAllDisplays) .SetMethod("getDisplayNearestPoint", &Screen::GetDisplayNearestPoint) - .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching); + .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching) + .SetMethod("getDesktopCapturer", &Screen::GetDesktopCapturer); } // static diff --git a/atom/browser/api/atom_api_screen.h b/atom/browser/api/atom_api_screen.h index f724130fa7fc..619f6c8b3f7c 100644 --- a/atom/browser/api/atom_api_screen.h +++ b/atom/browser/api/atom_api_screen.h @@ -6,7 +6,9 @@ #define ATOM_BROWSER_API_ATOM_API_SCREEN_H_ #include +#include +#include "atom/browser/api/atom_api_desktop_capturer.h" #include "atom/browser/api/event_emitter.h" #include "native_mate/handle.h" #include "ui/gfx/display_observer.h" @@ -36,6 +38,9 @@ class Screen : public mate::EventEmitter, gfx::Display GetDisplayNearestPoint(const gfx::Point& point); gfx::Display GetDisplayMatching(const gfx::Rect& match_rect); + mate::Handle GetDesktopCapturer( + const std::vector& sources); + // gfx::DisplayObserver: void OnDisplayAdded(const gfx::Display& new_display) override; void OnDisplayRemoved(const gfx::Display& old_display) override; diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index 18c80dc2b1f4..9ff568cf8d26 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -3,17 +3,18 @@ EventEmitter = require('events').EventEmitter bindings = process.atomBinding 'app' sessionBindings = process.atomBinding 'session' downloadItemBindings = process.atomBinding 'download_item' +desktopCapturerBindings = process.atomBinding 'desktop_capturer' app = bindings.app app.__proto__ = EventEmitter.prototype -wrapSession = (session) -> - # session is an Event Emitter. - session.__proto__ = EventEmitter.prototype +wrapToEventListener = (item) -> + # item is an Event Emitter. + item.__proto__ = EventEmitter.prototype wrapDownloadItem = (download_item) -> # download_item is an Event Emitter. - download_item.__proto__ = EventEmitter.prototype + wrapToEventListener download_item # Be compatible with old APIs. download_item.url = download_item.getUrl() download_item.filename = download_item.getFilename() @@ -58,11 +59,14 @@ app.resolveProxy = -> @defaultSession.resolveProxy.apply @defaultSession, argume app.on 'activate', (event, hasVisibleWindows) -> @emit 'activate-with-no-open-windows' if not hasVisibleWindows # Session wrapper. -sessionBindings._setWrapSession wrapSession +sessionBindings._setWrapSession wrapToEventListener process.once 'exit', sessionBindings._clearWrapSession downloadItemBindings._setWrapDownloadItem wrapDownloadItem process.once 'exit', downloadItemBindings._clearWrapDownloadItem +desktopCapturerBindings._setWrapDesktopCapturer wrapToEventListener +process.once 'exit', desktopCapturerBindings._clearWrapDesktopCapturer + # Only one App object pemitted. module.exports = app diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 2da68854ad14..9d2004deec8b 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -34,6 +34,7 @@ REFERENCE_MODULE(atom_browser_app); REFERENCE_MODULE(atom_browser_auto_updater); REFERENCE_MODULE(atom_browser_content_tracing); REFERENCE_MODULE(atom_browser_dialog); +REFERENCE_MODULE(atom_browser_desktop_capturer); REFERENCE_MODULE(atom_browser_download_item); REFERENCE_MODULE(atom_browser_menu); REFERENCE_MODULE(atom_browser_power_monitor); diff --git a/chromium_src/chrome/browser/media/desktop_media_list.h b/chromium_src/chrome/browser/media/desktop_media_list.h new file mode 100644 index 000000000000..fa0dbd815749 --- /dev/null +++ b/chromium_src/chrome/browser/media/desktop_media_list.h @@ -0,0 +1,60 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ +#define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ + +#include "base/basictypes.h" +#include "base/time/time.h" +#include "content/public/browser/desktop_media_id.h" +#include "ui/gfx/image/image_skia.h" + +class DesktopMediaListObserver; + +// DesktopMediaList provides the list of desktop media source (screens, windows, +// tabs), and their thumbnails, to the desktop media picker dialog. It +// transparently updates the list in the background, and notifies the desktop +// media picker when something changes. +class DesktopMediaList { + public: + // Struct used to represent each entry in the list. + struct Source { + // Id of the source. + content::DesktopMediaID id; + + // Name of the source that should be shown to the user. + base::string16 name; + + // The thumbnail for the source. + gfx::ImageSkia thumbnail; + }; + + virtual ~DesktopMediaList() {} + + // Sets time interval between updates. By default list of sources and their + // thumbnail are updated once per second. If called after StartUpdating() then + // it will take effect only after the next update. + virtual void SetUpdatePeriod(base::TimeDelta period) = 0; + + // Sets size to which the thumbnails should be scaled. If called after + // StartUpdating() then some thumbnails may be still scaled to the old size + // until they are updated. + virtual void SetThumbnailSize(const gfx::Size& thumbnail_size) = 0; + + // Sets ID of the hosting desktop picker dialog. The window with this ID will + // be filtered out from the list of sources. + virtual void SetViewDialogWindowId(content::DesktopMediaID::Id dialog_id) = 0; + + // Starts updating the model. The model is initially empty, so OnSourceAdded() + // notifications will be generated for each existing source as it is + // enumerated. After the initial enumeration the model will be refreshed based + // on the update period, and notifications generated only for changes in the + // model. + virtual void StartUpdating(DesktopMediaListObserver* observer) = 0; + + virtual int GetSourceCount() const = 0; + virtual const Source& GetSource(int index) const = 0; +}; + +#endif // CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ diff --git a/chromium_src/chrome/browser/media/desktop_media_list_observer.h b/chromium_src/chrome/browser/media/desktop_media_list_observer.h new file mode 100644 index 000000000000..1988f52b5bca --- /dev/null +++ b/chromium_src/chrome/browser/media/desktop_media_list_observer.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_OBSERVER_H_ +#define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_OBSERVER_H_ + +// Interface implemented by the desktop media picker dialog to receive +// notifications about changes in DesktopMediaList. +class DesktopMediaListObserver { + public: + virtual void OnSourceAdded(int index) = 0; + virtual void OnSourceRemoved(int index) = 0; + virtual void OnSourceMoved(int old_index, int new_index) = 0; + virtual void OnSourceNameChanged(int index) = 0; + virtual void OnSourceThumbnailChanged(int index) = 0; + + protected: + virtual ~DesktopMediaListObserver() {} +}; + +#endif // CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_OBSERVER_H_ diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc new file mode 100644 index 000000000000..1db22ab72db4 --- /dev/null +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -0,0 +1,366 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media/native_desktop_media_list.h" + +#include +#include +#include + +#include "base/hash.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/sequenced_worker_pool.h" +#include "chrome/browser/media/desktop_media_list_observer.h" +#include "content/public/browser/browser_thread.h" +#include "media/base/video_util.h" +#include "third_party/libyuv/include/libyuv/scale_argb.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" +#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" +#include "third_party/webrtc/modules/desktop_capture/window_capturer.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/skia_util.h" + +using content::BrowserThread; +using content::DesktopMediaID; + +namespace { + +// Update the list every second. +const int kDefaultUpdatePeriod = 1000; + +// Returns a hash of a DesktopFrame content to detect when image for a desktop +// media source has changed. +uint32 GetFrameHash(webrtc::DesktopFrame* frame) { + int data_size = frame->stride() * frame->size().height(); + return base::SuperFastHash(reinterpret_cast(frame->data()), data_size); +} + +gfx::ImageSkia ScaleDesktopFrame(scoped_ptr frame, + gfx::Size size) { + gfx::Rect scaled_rect = media::ComputeLetterboxRegion( + gfx::Rect(0, 0, size.width(), size.height()), + gfx::Size(frame->size().width(), frame->size().height())); + + SkBitmap result; + result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true); + result.lockPixels(); + + uint8* pixels_data = reinterpret_cast(result.getPixels()); + libyuv::ARGBScale(frame->data(), frame->stride(), + frame->size().width(), frame->size().height(), + pixels_data, result.rowBytes(), + scaled_rect.width(), scaled_rect.height(), + libyuv::kFilterBilinear); + + // Set alpha channel values to 255 for all pixels. + // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and + // remove this code. Currently screen/window capturers (at least some + // implementations) only capture R, G and B channels and set Alpha to 0. + // crbug.com/264424 + for (int y = 0; y < result.height(); ++y) { + for (int x = 0; x < result.width(); ++x) { + pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] = + 0xff; + } + } + + result.unlockPixels(); + + return gfx::ImageSkia::CreateFrom1xBitmap(result); +} + +} // namespace + +NativeDesktopMediaList::SourceDescription::SourceDescription( + DesktopMediaID id, + const base::string16& name) + : id(id), + name(name) { +} + +class NativeDesktopMediaList::Worker + : public webrtc::DesktopCapturer::Callback { + public: + Worker(base::WeakPtr media_list, + scoped_ptr screen_capturer, + scoped_ptr window_capturer); + ~Worker() override; + + void Refresh(const gfx::Size& thumbnail_size, + content::DesktopMediaID::Id view_dialog_id); + + private: + typedef std::map ImageHashesMap; + + // webrtc::DesktopCapturer::Callback interface. + webrtc::SharedMemory* CreateSharedMemory(size_t size) override; + void OnCaptureCompleted(webrtc::DesktopFrame* frame) override; + + base::WeakPtr media_list_; + + scoped_ptr screen_capturer_; + scoped_ptr window_capturer_; + + scoped_ptr current_frame_; + + ImageHashesMap image_hashes_; + + DISALLOW_COPY_AND_ASSIGN(Worker); +}; + +NativeDesktopMediaList::Worker::Worker( + base::WeakPtr media_list, + scoped_ptr screen_capturer, + scoped_ptr window_capturer) + : media_list_(media_list), + screen_capturer_(screen_capturer.Pass()), + window_capturer_(window_capturer.Pass()) { + if (screen_capturer_) + screen_capturer_->Start(this); + if (window_capturer_) + window_capturer_->Start(this); +} + +NativeDesktopMediaList::Worker::~Worker() {} + +void NativeDesktopMediaList::Worker::Refresh( + const gfx::Size& thumbnail_size, + content::DesktopMediaID::Id view_dialog_id) { + std::vector sources; + + if (screen_capturer_) { + webrtc::ScreenCapturer::ScreenList screens; + if (screen_capturer_->GetScreenList(&screens)) { + bool mutiple_screens = screens.size() > 1; + base::string16 title; + for (size_t i = 0; i < screens.size(); ++i) { + if (mutiple_screens) { + title = base::UTF8ToUTF16("Screen " + base::IntToString(i+1)); + } else { + title = base::UTF8ToUTF16("Entire screen"); + } + sources.push_back(SourceDescription(DesktopMediaID( + DesktopMediaID::TYPE_SCREEN, screens[i].id), title)); + } + } + } + + if (window_capturer_) { + webrtc::WindowCapturer::WindowList windows; + if (window_capturer_->GetWindowList(&windows)) { + for (webrtc::WindowCapturer::WindowList::iterator it = windows.begin(); + it != windows.end(); ++it) { + // Skip the picker dialog window. + if (it->id != view_dialog_id) { + sources.push_back(SourceDescription( + DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id), + base::UTF8ToUTF16(it->title))); + } + } + } + } + // Update list of windows before updating thumbnails. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::OnSourcesList, + media_list_, sources)); + + ImageHashesMap new_image_hashes; + + // Get a thumbnail for each source. + for (size_t i = 0; i < sources.size(); ++i) { + SourceDescription& source = sources[i]; + switch (source.id.type) { + case DesktopMediaID::TYPE_SCREEN: + if (!screen_capturer_->SelectScreen(source.id.id)) + continue; + screen_capturer_->Capture(webrtc::DesktopRegion()); + break; + + case DesktopMediaID::TYPE_WINDOW: + if (!window_capturer_->SelectWindow(source.id.id)) + continue; + window_capturer_->Capture(webrtc::DesktopRegion()); + break; + + default: + NOTREACHED(); + } + + // Expect that DesktopCapturer to always captures frames synchronously. + // |current_frame_| may be NULL if capture failed (e.g. because window has + // been closed). + if (current_frame_) { + uint32 frame_hash = GetFrameHash(current_frame_.get()); + new_image_hashes[source.id] = frame_hash; + + // Scale the image only if it has changed. + ImageHashesMap::iterator it = image_hashes_.find(source.id); + if (it == image_hashes_.end() || it->second != frame_hash) { + gfx::ImageSkia thumbnail = + ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::OnSourceThumbnail, + media_list_, i, thumbnail)); + } + } + } + + image_hashes_.swap(new_image_hashes); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::OnRefreshFinished, media_list_)); +} + +webrtc::SharedMemory* NativeDesktopMediaList::Worker::CreateSharedMemory( + size_t size) { + return NULL; +} + +void NativeDesktopMediaList::Worker::OnCaptureCompleted( + webrtc::DesktopFrame* frame) { + current_frame_.reset(frame); +} + +NativeDesktopMediaList::NativeDesktopMediaList( + scoped_ptr screen_capturer, + scoped_ptr window_capturer) + : screen_capturer_(screen_capturer.Pass()), + window_capturer_(window_capturer.Pass()), + update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)), + thumbnail_size_(100, 100), + view_dialog_id_(-1), + observer_(NULL), + weak_factory_(this) { + base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool(); + capture_task_runner_ = worker_pool->GetSequencedTaskRunner( + worker_pool->GetSequenceToken()); +} + +NativeDesktopMediaList::~NativeDesktopMediaList() { + capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release()); +} + +void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) { + DCHECK(!observer_); + update_period_ = period; +} + +void NativeDesktopMediaList::SetThumbnailSize( + const gfx::Size& thumbnail_size) { + thumbnail_size_ = thumbnail_size; +} + +void NativeDesktopMediaList::SetViewDialogWindowId( + content::DesktopMediaID::Id dialog_id) { + view_dialog_id_ = dialog_id; +} + +void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) { + DCHECK(!observer_); + DCHECK(screen_capturer_ || window_capturer_); + + observer_ = observer; + + worker_.reset(new Worker(weak_factory_.GetWeakPtr(), + screen_capturer_.Pass(), window_capturer_.Pass())); + Refresh(); +} + +int NativeDesktopMediaList::GetSourceCount() const { + return sources_.size(); +} + +const DesktopMediaList::Source& NativeDesktopMediaList::GetSource( + int index) const { + return sources_[index]; +} + +void NativeDesktopMediaList::Refresh() { + capture_task_runner_->PostTask( + FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()), + thumbnail_size_, view_dialog_id_)); +} + +void NativeDesktopMediaList::OnSourcesList( + const std::vector& new_sources) { + typedef std::set SourceSet; + SourceSet new_source_set; + for (size_t i = 0; i < new_sources.size(); ++i) { + new_source_set.insert(new_sources[i].id); + } + // Iterate through the old sources to find the removed sources. + for (size_t i = 0; i < sources_.size(); ++i) { + if (new_source_set.find(sources_[i].id) == new_source_set.end()) { + sources_.erase(sources_.begin() + i); + observer_->OnSourceRemoved(i); + --i; + } + } + // Iterate through the new sources to find the added sources. + if (new_sources.size() > sources_.size()) { + SourceSet old_source_set; + for (size_t i = 0; i < sources_.size(); ++i) { + old_source_set.insert(sources_[i].id); + } + + for (size_t i = 0; i < new_sources.size(); ++i) { + if (old_source_set.find(new_sources[i].id) == old_source_set.end()) { + sources_.insert(sources_.begin() + i, Source()); + sources_[i].id = new_sources[i].id; + sources_[i].name = new_sources[i].name; + observer_->OnSourceAdded(i); + } + } + } + DCHECK_EQ(new_sources.size(), sources_.size()); + + // Find the moved/changed sources. + size_t pos = 0; + while (pos < sources_.size()) { + if (!(sources_[pos].id == new_sources[pos].id)) { + // Find the source that should be moved to |pos|, starting from |pos + 1| + // of |sources_|, because entries before |pos| should have been sorted. + size_t old_pos = pos + 1; + for (; old_pos < sources_.size(); ++old_pos) { + if (sources_[old_pos].id == new_sources[pos].id) + break; + } + DCHECK(sources_[old_pos].id == new_sources[pos].id); + + // Move the source from |old_pos| to |pos|. + Source temp = sources_[old_pos]; + sources_.erase(sources_.begin() + old_pos); + sources_.insert(sources_.begin() + pos, temp); + + observer_->OnSourceMoved(old_pos, pos); + } + + if (sources_[pos].name != new_sources[pos].name) { + sources_[pos].name = new_sources[pos].name; + observer_->OnSourceNameChanged(pos); + } + ++pos; + } +} + +void NativeDesktopMediaList::OnSourceThumbnail( + int index, + const gfx::ImageSkia& image) { + DCHECK_LT(index, static_cast(sources_.size())); + sources_[index].thumbnail = image; + observer_->OnSourceThumbnailChanged(index); +} + +void NativeDesktopMediaList::OnRefreshFinished() { + BrowserThread::PostDelayedTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::Refresh, + weak_factory_.GetWeakPtr()), + update_period_); +} diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.h b/chromium_src/chrome/browser/media/native_desktop_media_list.h new file mode 100644 index 000000000000..81bd32152813 --- /dev/null +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.h @@ -0,0 +1,100 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ +#define CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner.h" +#include "chrome/browser/media/desktop_media_list.h" +#include "content/public/browser/desktop_media_id.h" +#include "ui/gfx/image/image_skia.h" + +namespace webrtc { +class ScreenCapturer; +class WindowCapturer; +} + +// Implementation of DesktopMediaList that shows native screens and +// native windows. +class NativeDesktopMediaList : public DesktopMediaList { + public: + // Caller may pass NULL for either of the arguments in case when only some + // types of sources the model should be populated with (e.g. it will only + // contain windows, if |screen_capturer| is NULL). + NativeDesktopMediaList( + scoped_ptr screen_capturer, + scoped_ptr window_capturer); + ~NativeDesktopMediaList() override; + + // DesktopMediaList interface. + void SetUpdatePeriod(base::TimeDelta period) override; + void SetThumbnailSize(const gfx::Size& thumbnail_size) override; + void StartUpdating(DesktopMediaListObserver* observer) override; + int GetSourceCount() const override; + const Source& GetSource(int index) const override; + void SetViewDialogWindowId(content::DesktopMediaID::Id dialog_id) override; + + private: + class Worker; + friend class Worker; + + // Struct used to represent sources list the model gets from the Worker. + struct SourceDescription { + SourceDescription(content::DesktopMediaID id, const base::string16& name); + + content::DesktopMediaID id; + base::string16 name; + }; + + // Order comparator for sources. Used to sort list of sources. + static bool CompareSources(const SourceDescription& a, + const SourceDescription& b); + + // Post a task for the |worker_| to update list of windows and get thumbnails. + void Refresh(); + + // Called by |worker_| to refresh the model. First it posts tasks for + // OnSourcesList() with the fresh list of sources, then follows with + // OnSourceThumbnail() for each changed thumbnail and then calls + // OnRefreshFinished() at the end. + void OnSourcesList(const std::vector& sources); + void OnSourceThumbnail(int index, const gfx::ImageSkia& thumbnail); + void OnRefreshFinished(); + + // Capturers specified in SetCapturers() and passed to the |worker_| later. + scoped_ptr screen_capturer_; + scoped_ptr window_capturer_; + + // Time interval between mode updates. + base::TimeDelta update_period_; + + // Size of thumbnails generated by the model. + gfx::Size thumbnail_size_; + + // ID of the hosting dialog. + content::DesktopMediaID::Id view_dialog_id_; + + // The observer passed to StartUpdating(). + DesktopMediaListObserver* observer_; + + // Task runner used for the |worker_|. + scoped_refptr capture_task_runner_; + + // An object that does all the work of getting list of sources on a background + // thread (see |capture_task_runner_|). Destroyed on |capture_task_runner_| + // after the model is destroyed. + scoped_ptr worker_; + + // Current list of sources. + std::vector sources_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(NativeDesktopMediaList); +}; + +#endif // CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ diff --git a/filenames.gypi b/filenames.gypi index cb6a2273eae3..81d4d87d3aab 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -71,6 +71,8 @@ 'atom/browser/api/atom_api_content_tracing.cc', 'atom/browser/api/atom_api_cookies.cc', 'atom/browser/api/atom_api_cookies.h', + 'atom/browser/api/atom_api_desktop_capturer.cc', + 'atom/browser/api/atom_api_desktop_capturer.h', 'atom/browser/api/atom_api_download_item.cc', 'atom/browser/api/atom_api_download_item.h', 'atom/browser/api/atom_api_dialog.cc', @@ -349,6 +351,10 @@ 'chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h', + 'chromium_src/chrome/browser/media/desktop_media_list.h', + 'chromium_src/chrome/browser/media/desktop_media_list_observer.h', + 'chromium_src/chrome/browser/media/native_desktop_media_list.cc', + 'chromium_src/chrome/browser/media/native_desktop_media_list.h', 'chromium_src/chrome/browser/printing/print_job.cc', 'chromium_src/chrome/browser/printing/print_job.h', 'chromium_src/chrome/browser/printing/print_job_manager.cc',