From c9fbde321c5595430fa869774f4bb942470e9221 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Fri, 2 Oct 2015 17:50:10 +0800 Subject: [PATCH 01/20] 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', From 48fbd4741645f2518fc8138d886bddded4ba363e Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sat, 3 Oct 2015 10:41:08 +0800 Subject: [PATCH 02/20] Make desktop capture API work on Windows. --- atom.gyp | 11 +++++++++++ atom/browser/api/atom_api_desktop_capturer.cc | 17 +++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/atom.gyp b/atom.gyp index ed885743c6a6..77ad2e22ae39 100644 --- a/atom.gyp +++ b/atom.gyp @@ -275,8 +275,14 @@ '-lcomctl32.lib', '-lcomdlg32.lib', '-lwininet.lib', + '-lwinmm.lib', ], }, + 'defines': [ + # The usage of "webrtc/modules/desktop_capture/desktop_capture_options.h" + # is required to see this macro. + 'WEBRTC_WIN', + ], 'dependencies': [ # Node is built as static_library on Windows, so we also need to # include its dependencies here. @@ -290,6 +296,11 @@ ], }], # OS=="win" ['OS=="mac"', { + 'defines': [ + # The usage of "webrtc/modules/desktop_capture/desktop_capture_options.h" + # is required to see this macro. + 'WEBRTC_MAC', + ], 'dependencies': [ 'vendor/crashpad/client/client.gyp:crashpad_client', 'vendor/crashpad/handler/handler.gyp:crashpad_handler', diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index c3a573715703..96786d098912 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -47,10 +47,23 @@ const int kThumbnailHeight = 150; } // namespace DesktopCapturer::DesktopCapturer(bool show_screens, bool show_windows) { + webrtc::DesktopCaptureOptions options = + webrtc::DesktopCaptureOptions::CreateDefault(); + +#if defined(OS_WIN) + // On windows, desktop effects (e.g. Aero) will be disabled when the Desktop + // capture API is active by default. + // We keep the desktop effects in most times. Howerver, the screen still + // fickers when the API is capturing the window due to limitation of current + // implemetation. This is a known and wontFix issue in webrtc (see: + // http://code.google.com/p/webrtc/issues/detail?id=3373) + options.set_disable_effects(false); +#endif + scoped_ptr screen_capturer( - show_screens ? webrtc::ScreenCapturer::Create() : nullptr); + show_screens ? webrtc::ScreenCapturer::Create(options) : nullptr); scoped_ptr window_capturer( - show_windows ? webrtc::WindowCapturer::Create() : nullptr); + show_windows ? webrtc::WindowCapturer::Create(options) : nullptr); media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight)); From 1e69ef79de55fd4d26161fbb808edef8574c778c Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sun, 4 Oct 2015 09:35:00 +0800 Subject: [PATCH 03/20] Refine: make desktop-capturer as a renderer module. --- atom/browser/api/atom_api_desktop_capturer.cc | 67 +++++----- atom/browser/api/atom_api_desktop_capturer.h | 17 +-- atom/browser/api/atom_api_screen.cc | 18 +-- atom/browser/api/atom_api_screen.h | 5 - atom/browser/api/lib/app.coffee | 14 +-- atom/browser/lib/desktop-capturer.coffee | 42 +++++++ atom/browser/lib/init.coffee | 3 + atom/renderer/api/lib/desktop-capturer.coffee | 22 ++++ .../media/native_desktop_media_list.cc | 2 +- docs/README.md | 1 + docs/api/desktop-capturer.md | 118 ++++++++++++++++++ filenames.gypi | 2 + 12 files changed, 237 insertions(+), 74 deletions(-) create mode 100644 atom/browser/lib/desktop-capturer.coffee create mode 100644 atom/renderer/api/lib/desktop-capturer.coffee create mode 100644 docs/api/desktop-capturer.md diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index 96786d098912..f3e05486e9b3 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -5,7 +5,6 @@ #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" @@ -25,7 +24,8 @@ struct Converter { content::DesktopMediaID id = source.id; dict.Set("name", base::UTF16ToUTF8(source.name)); dict.Set("id", id.ToString()); - dict.Set("thumbnail", + dict.Set( + "thumbnail", atom::api::NativeImage::Create(isolate, gfx::Image(source.thumbnail))); return ConvertToV8(isolate, dict); } @@ -38,15 +38,29 @@ 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) { +DesktopCapturer::DesktopCapturer() { +} + +DesktopCapturer::~DesktopCapturer() { +} + +void DesktopCapturer::StartUpdating(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; + } + + if (!show_windows && !show_screens) + return; + webrtc::DesktopCaptureOptions options = webrtc::DesktopCaptureOptions::CreateDefault(); @@ -70,54 +84,39 @@ DesktopCapturer::DesktopCapturer(bool show_screens, bool show_windows) { media_list_->StartUpdating(this); } -DesktopCapturer::~DesktopCapturer() { -} - -const DesktopMediaList::Source& DesktopCapturer::GetSource(int index) { - return media_list_->GetSource(index); +void DesktopCapturer::StopUpdating() { + media_list_.reset(); } void DesktopCapturer::OnSourceAdded(int index) { - Emit("source-added", index); + Emit("source-added", media_list_->GetSource(index)); } void DesktopCapturer::OnSourceRemoved(int index) { - Emit("source-removed", index); + Emit("source-removed", media_list_->GetSource(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); + Emit("source-name-changed", media_list_->GetSource(index)); } void DesktopCapturer::OnSourceThumbnailChanged(int index) { - Emit("source-thumbnail-changed", index); + Emit("source-thumbnail-changed", media_list_->GetSource(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(); + .SetMethod("startUpdating", &DesktopCapturer::StartUpdating) + .SetMethod("stopUpdating", &DesktopCapturer::StopUpdating); } // 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; +mate::Handle DesktopCapturer::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new DesktopCapturer); } } // namespace api @@ -130,9 +129,7 @@ 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); + dict.Set("desktopCapturer", atom::api::DesktopCapturer::Create(isolate)); } } // namespace diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index aae0a8268b9d..f4be410ed619 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -2,8 +2,11 @@ // 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_ +#ifndef ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ +#define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ + +#include +#include #include "base/memory/scoped_ptr.h" #include "atom/browser/api/event_emitter.h" @@ -18,13 +21,13 @@ namespace api { class DesktopCapturer: public mate::EventEmitter, public DesktopMediaListObserver { public: - static mate::Handle Create(v8::Isolate* isolate, - bool show_screens, bool show_windows); + static mate::Handle Create(v8::Isolate* isolate); - const DesktopMediaList::Source& GetSource(int index); + void StartUpdating(const std::vector& sources); + void StopUpdating(); protected: - DesktopCapturer(bool show_screens, bool show_windows); + DesktopCapturer(); ~DesktopCapturer(); // DesktopMediaListObserver overrides. @@ -48,4 +51,4 @@ class DesktopCapturer: public mate::EventEmitter, } // namespace atom -#endif // ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_ +#endif // ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ diff --git a/atom/browser/api/atom_api_screen.cc b/atom/browser/api/atom_api_screen.cc index 761ef28ef97e..b73bda9ced84 100644 --- a/atom/browser/api/atom_api_screen.cc +++ b/atom/browser/api/atom_api_screen.cc @@ -56,21 +56,6 @@ 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(); } @@ -122,8 +107,7 @@ mate::ObjectTemplateBuilder Screen::GetObjectTemplateBuilder( .SetMethod("getPrimaryDisplay", &Screen::GetPrimaryDisplay) .SetMethod("getAllDisplays", &Screen::GetAllDisplays) .SetMethod("getDisplayNearestPoint", &Screen::GetDisplayNearestPoint) - .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching) - .SetMethod("getDesktopCapturer", &Screen::GetDesktopCapturer); + .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching); } // static diff --git a/atom/browser/api/atom_api_screen.h b/atom/browser/api/atom_api_screen.h index 619f6c8b3f7c..f724130fa7fc 100644 --- a/atom/browser/api/atom_api_screen.h +++ b/atom/browser/api/atom_api_screen.h @@ -6,9 +6,7 @@ #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" @@ -38,9 +36,6 @@ 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 9ff568cf8d26..18c80dc2b1f4 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -3,18 +3,17 @@ 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 -wrapToEventListener = (item) -> - # item is an Event Emitter. - item.__proto__ = EventEmitter.prototype +wrapSession = (session) -> + # session is an Event Emitter. + session.__proto__ = EventEmitter.prototype wrapDownloadItem = (download_item) -> # download_item is an Event Emitter. - wrapToEventListener download_item + download_item.__proto__ = EventEmitter.prototype # Be compatible with old APIs. download_item.url = download_item.getUrl() download_item.filename = download_item.getFilename() @@ -59,14 +58,11 @@ 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 wrapToEventListener +sessionBindings._setWrapSession wrapSession 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/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee new file mode 100644 index 000000000000..29bb1ae60518 --- /dev/null +++ b/atom/browser/lib/desktop-capturer.coffee @@ -0,0 +1,42 @@ +ipc = require 'ipc' +BrowserWindow = require 'browser-window' +EventEmitter = require('events').EventEmitter + +# The browser module manages all desktop-capturer moduels in renderer process. +desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer +desktopCapturer.__proto__ = EventEmitter.prototype + +getWebContentsFromId = (id) -> + windows = BrowserWindow.getAllWindows() + return window.webContents for window in windows when window.webContents?.getId() == id + +# The set for tracking id of webContents. +webContentsIds = new Set + +stopDesktopCapture = (id) -> + webContentsIds.delete id + if webContentsIds.size is 0 + desktopCapturer.stopUpdating() + +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED', (event) -> + id = event.sender.getId() + # Remove the tracked webContents when it is destroyed. + getWebContentsFromId(id).on 'destroyed', ()-> + stopDesktopCapture id + event.returnValue = 'done' + +# Handle `desktopCapturer.startUpdating` API. +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', (event, args) -> + id = event.sender.getId() + webContentsIds.add id + desktopCapturer.startUpdating args + +# Handle `desktopCapturer.stopUpdating` API. +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING', (event) -> + stopDesktopCapture event.sender.getId() + +for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"] + do (event_name) -> + desktopCapturer.on event_name, (event, source) -> + webContentsIds.forEach (id) -> + getWebContentsFromId(id).send event_name, { id: source.id, name: source.name, dataUrl: source.thumbnail.toDataUrl() } diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index 1299364d2fa6..6caaaf4bf056 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -92,6 +92,9 @@ app.setAppPath packagePath # Load the chrome extension support. require './chrome-extension' +# Load internal desktop-capturer module. +require './desktop-capturer' + # Set main startup script of the app. mainStartupScript = packageJson.main or 'index.js' diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee new file mode 100644 index 000000000000..bf2d27597a08 --- /dev/null +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -0,0 +1,22 @@ +ipc = require 'ipc' +remote = require 'remote' +NativeImage = require 'native-image' + +EventEmitter = require('events').EventEmitter +desktopCapturer = new EventEmitter + +# Tells main process the renderer is requiring 'desktop-capture' module. +ipc.sendSync 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED' + +desktopCapturer.startUpdating = (args) -> + ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', args + +desktopCapturer.stopUpdating = () -> + ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING' + +for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"] + do (event_name) -> + ipc.on event_name, (source) -> + desktopCapturer.emit event_name, { id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.dataUrl } + +module.exports = desktopCapturer diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index 1db22ab72db4..050c5e1d5a78 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -297,8 +297,8 @@ void NativeDesktopMediaList::OnSourcesList( // 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); + sources_.erase(sources_.begin() + i); --i; } } diff --git a/docs/README.md b/docs/README.md index b2f95fe4b579..cc25e9d0ff66 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,6 +46,7 @@ ### Modules for the Renderer Process (Web Page): +* [desktop-capturer](api/desktop-capturer.md) * [ipc (renderer)](api/ipc-renderer.md) * [remote](api/remote.md) * [web-frame](api/web-frame.md) diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md new file mode 100644 index 000000000000..8862762ddabb --- /dev/null +++ b/docs/api/desktop-capturer.md @@ -0,0 +1,118 @@ +# desktop-capturer + +The `desktop-capturer` is a renderer module used to capture the content of +screen and individual app windows. + +```javascript +// In the renderer process. +var desktopCapturer = require('desktop-capturer'); + +desktopCapturer.on('source-added', function(source) { + console.log("source " + source.name + " is added."); + // source.thumbnail is not ready to use here and webkitGetUserMedia neither. +}); + +desktopCapturer.on('source-thumbnail-changed', function(source) { + if (source.name == "Electron") { + // stopUpdating since we have found the window that we want to capture. + desktopCapturer.stopUpdating(); + + // It's ready to use webkitGetUserMedia right now. + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: source.id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } + } + }, gotStream, getUserMediaError); + } +}); + +// Let's start updating after setting all event of `desktopCapturer` +desktopCapturer.startUpdating(); + +function gotStream(stream) { + document.querySelector('video').src = URL.createObjectURL(stream); +} + +function getUserMediaError(e) { + console.log('getUserMediaError'); +} +``` + +## Events + +### Event: 'source-added' + +* `source` Source + +Emits when there is a new source added, usually a new window is created, +a new screen is attached. + +**Note:** The thumbnail of the source is not ready for use when 'source-added' +event is emitted, and `navigator.webkitGetUserMedia` neither. + +### Event: 'source-removed' + +* `source` Source + +Emits when there is a source removed. + +### Event: 'source-name-changed' + +* `source` Source + +Emits when the name of source is changed. + +### Event: 'source-thumbnail-changed' + +* `source` Source + +Emits when the thumbnail of source is changed. `desktopCapturer` will refresh +all sources every second. + +## Methods + +The `desktopCapturer` module has the following methods: + +### `desktopCapturer.startUpdating(options)` + +* `options` Array - An array of String that enums the types of desktop sources. + * `screen` String - Screen + * `window` String - Individual window + +Starts updating desktopCapturer. The events of `desktopCapturer` will only be +emitted after `startUpdating` API is invoked. + +**Note:** At beginning, the desktopCapturer is initially empty, so the +`source-added` event will be emitted for each existing source as it is +enumrated. +On Windows, you will see the screen ficker when desktopCapturer starts updating. +This is normal because the desktop effects(e.g. Aero) will be disabled when +desktop capturer is working. The ficker will disappear once +`desktopCapturer.stopUpdating()` is invoked. + +### `desktopCapturer.stopUpdating()` + +Stops updating desktopCapturer. The events of `desktopCapturer` will not be +emitted after the API is invoked. + +**Note:** It is a good practice to call `stopUpdating` when you do not need +getting any infomation of sources, usually after passing a id of source to +`navigator.webkitGetUserMedia`. + +## Source + +`Source` is an object represents a captured screen or individual window. It has +following properties: + +* `id` String - The id of the capturing window or screen used in + `navigator.webkitGetUserMedia`. +* `name` String - The descriped name of the capturing screen or window. +* `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. diff --git a/filenames.gypi b/filenames.gypi index 81d4d87d3aab..46e06bebd183 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -26,6 +26,7 @@ 'atom/browser/api/lib/tray.coffee', 'atom/browser/api/lib/web-contents.coffee', 'atom/browser/lib/chrome-extension.coffee', + 'atom/browser/lib/desktop-capturer.coffee', 'atom/browser/lib/guest-view-manager.coffee', 'atom/browser/lib/guest-window-manager.coffee', 'atom/browser/lib/init.coffee', @@ -45,6 +46,7 @@ 'atom/renderer/lib/web-view/web-view.coffee', 'atom/renderer/lib/web-view/web-view-attributes.coffee', 'atom/renderer/lib/web-view/web-view-constants.coffee', + 'atom/renderer/api/lib/desktop-capturer.coffee', 'atom/renderer/api/lib/ipc.coffee', 'atom/renderer/api/lib/remote.coffee', 'atom/renderer/api/lib/screen.coffee', From 36c0ad7fda2abf9476fd90c54580c2ed71edbffd Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Mon, 5 Oct 2015 11:32:12 +0800 Subject: [PATCH 04/20] Refine more about desktop capturer API. * Simplify the coffeescript code. * Add more options in desktopCapturer.startUpdating. --- atom.gyp | 10 --- atom/browser/api/atom_api_desktop_capturer.cc | 64 +++++++++++------- atom/browser/api/atom_api_desktop_capturer.h | 10 ++- atom/browser/lib/desktop-capturer.coffee | 26 ++++---- atom/renderer/api/lib/desktop-capturer.coffee | 11 ++-- .../media/desktop_media_list_observer.h | 1 + .../media/native_desktop_media_list.cc | 1 + docs/api/desktop-capturer.md | 65 +++++++++++-------- 8 files changed, 104 insertions(+), 84 deletions(-) diff --git a/atom.gyp b/atom.gyp index 77ad2e22ae39..b2e55e9be308 100644 --- a/atom.gyp +++ b/atom.gyp @@ -278,11 +278,6 @@ '-lwinmm.lib', ], }, - 'defines': [ - # The usage of "webrtc/modules/desktop_capture/desktop_capture_options.h" - # is required to see this macro. - 'WEBRTC_WIN', - ], 'dependencies': [ # Node is built as static_library on Windows, so we also need to # include its dependencies here. @@ -296,11 +291,6 @@ ], }], # OS=="win" ['OS=="mac"', { - 'defines': [ - # The usage of "webrtc/modules/desktop_capture/desktop_capture_options.h" - # is required to see this macro. - 'WEBRTC_MAC', - ], 'dependencies': [ 'vendor/crashpad/client/client.gyp:crashpad_client', 'vendor/crashpad/handler/handler.gyp:crashpad_handler', diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index f3e05486e9b3..a6556f6a89fa 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -14,30 +14,13 @@ #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 { +// Refresh every second. +const int kUpdatePeriod = 1000; const int kThumbnailWidth = 150; const int kThumbnailHeight = 150; } // namespace @@ -48,7 +31,11 @@ DesktopCapturer::DesktopCapturer() { DesktopCapturer::~DesktopCapturer() { } -void DesktopCapturer::StartUpdating(const std::vector& sources) { +void DesktopCapturer::StartUpdating(const mate::Dictionary& args) { + std::vector sources; + if (!args.Get("types", &sources)) + return; + bool show_screens = false; bool show_windows = false; for (const auto& source_type : sources) { @@ -80,7 +67,16 @@ void DesktopCapturer::StartUpdating(const std::vector& sources) { show_windows ? webrtc::WindowCapturer::Create(options) : nullptr); media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); - media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight)); + + int update_period = kUpdatePeriod; + int thumbnail_width = kThumbnailWidth, thumbnail_height = kThumbnailHeight; + args.Get("updatePeriod", &update_period); + args.Get("thumbnailWidth", &thumbnail_width); + args.Get("thumbnailHeight", &thumbnail_height); + + media_list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds( + update_period)); + media_list_->SetThumbnailSize(gfx::Size(thumbnail_width, thumbnail_height)); media_list_->StartUpdating(this); } @@ -89,22 +85,40 @@ void DesktopCapturer::StopUpdating() { } void DesktopCapturer::OnSourceAdded(int index) { - Emit("source-added", media_list_->GetSource(index)); + EmitDesktopCapturerEvent("source-added", index, false); } void DesktopCapturer::OnSourceRemoved(int index) { - Emit("source-removed", media_list_->GetSource(index)); + EmitDesktopCapturerEvent("source-removed", index, false); } +// Ignore this event. void DesktopCapturer::OnSourceMoved(int old_index, int new_index) { } void DesktopCapturer::OnSourceNameChanged(int index) { - Emit("source-name-changed", media_list_->GetSource(index)); + EmitDesktopCapturerEvent("source-removed", index, false); } void DesktopCapturer::OnSourceThumbnailChanged(int index) { - Emit("source-thumbnail-changed", media_list_->GetSource(index)); + EmitDesktopCapturerEvent("source-thumbnail-changed", index, true); +} + +void DesktopCapturer::OnRefreshFinished() { + Emit("refresh-finished"); +} + +void DesktopCapturer::EmitDesktopCapturerEvent( + const std::string& event_name, int index, bool with_thumbnail) { + const DesktopMediaList::Source& source = media_list_->GetSource(index); + content::DesktopMediaID id = source.id; + if (!with_thumbnail) + Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name)); + else { + Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name), + atom::api::NativeImage::Create(isolate(), + gfx::Image(source.thumbnail))); + } } mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index f4be410ed619..f0620c8e9cf8 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -14,6 +14,10 @@ #include "chrome/browser/media/native_desktop_media_list.h" #include "native_mate/handle.h" +namespace mate { +class Dictionary; +} + namespace atom { namespace api { @@ -23,7 +27,8 @@ class DesktopCapturer: public mate::EventEmitter, public: static mate::Handle Create(v8::Isolate* isolate); - void StartUpdating(const std::vector& sources); + void StartUpdating(const mate::Dictionary& args); + void StopUpdating(); protected: @@ -36,8 +41,11 @@ class DesktopCapturer: public mate::EventEmitter, void OnSourceMoved(int old_index, int new_index) override; void OnSourceNameChanged(int index) override; void OnSourceThumbnailChanged(int index) override; + void OnRefreshFinished() override; private: + void EmitDesktopCapturerEvent( + const std::string& event_name, int index, bool with_thumbnail); // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 29bb1ae60518..0177d9721bb5 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -1,10 +1,8 @@ ipc = require 'ipc' BrowserWindow = require 'browser-window' -EventEmitter = require('events').EventEmitter # The browser module manages all desktop-capturer moduels in renderer process. desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer -desktopCapturer.__proto__ = EventEmitter.prototype getWebContentsFromId = (id) -> windows = BrowserWindow.getAllWindows() @@ -15,28 +13,26 @@ webContentsIds = new Set stopDesktopCapture = (id) -> webContentsIds.delete id + # Stop updating if no renderer process listens the desktop capturer. if webContentsIds.size is 0 desktopCapturer.stopUpdating() -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED', (event) -> - id = event.sender.getId() - # Remove the tracked webContents when it is destroyed. - getWebContentsFromId(id).on 'destroyed', ()-> - stopDesktopCapture id - event.returnValue = 'done' - # Handle `desktopCapturer.startUpdating` API. ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', (event, args) -> id = event.sender.getId() + if not webContentsIds.has id + # Stop sending desktop capturer events to the destroyed webContents. + event.sender.on 'destroyed', ()-> + stopDesktopCapture id + # Start updating the desktopCapturer if it doesn't. + if webContentsIds.size is 0 + desktopCapturer.startUpdating args webContentsIds.add id - desktopCapturer.startUpdating args # Handle `desktopCapturer.stopUpdating` API. ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING', (event) -> stopDesktopCapture event.sender.getId() -for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"] - do (event_name) -> - desktopCapturer.on event_name, (event, source) -> - webContentsIds.forEach (id) -> - getWebContentsFromId(id).send event_name, { id: source.id, name: source.name, dataUrl: source.thumbnail.toDataUrl() } +desktopCapturer.emit = (event_name, event, desktopId, name, thumbnail) -> + webContentsIds.forEach (id) -> + getWebContentsFromId(id).send 'ATOM_RENDERER_DESKTOP_CAPTURER', event_name, desktopId, name, thumbnail?.toDataUrl() diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index bf2d27597a08..ae5e054c2a75 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -5,18 +5,15 @@ NativeImage = require 'native-image' EventEmitter = require('events').EventEmitter desktopCapturer = new EventEmitter -# Tells main process the renderer is requiring 'desktop-capture' module. -ipc.sendSync 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED' - desktopCapturer.startUpdating = (args) -> ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', args desktopCapturer.stopUpdating = () -> ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING' -for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"] - do (event_name) -> - ipc.on event_name, (source) -> - desktopCapturer.emit event_name, { id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.dataUrl } +ipc.on 'ATOM_RENDERER_DESKTOP_CAPTURER', (event_name, id, name, thumbnail) -> + if not thumbnail + return desktopCapturer.emit event_name, id, name + desktopCapturer.emit event_name, id, name, NativeImage.createFromDataUrl thumbnail module.exports = desktopCapturer diff --git a/chromium_src/chrome/browser/media/desktop_media_list_observer.h b/chromium_src/chrome/browser/media/desktop_media_list_observer.h index 1988f52b5bca..eccaa407d2df 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list_observer.h +++ b/chromium_src/chrome/browser/media/desktop_media_list_observer.h @@ -14,6 +14,7 @@ class DesktopMediaListObserver { virtual void OnSourceMoved(int old_index, int new_index) = 0; virtual void OnSourceNameChanged(int index) = 0; virtual void OnSourceThumbnailChanged(int index) = 0; + virtual void OnRefreshFinished() = 0; protected: virtual ~DesktopMediaListObserver() {} diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index 050c5e1d5a78..a326ded91b80 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -358,6 +358,7 @@ void NativeDesktopMediaList::OnSourceThumbnail( } void NativeDesktopMediaList::OnRefreshFinished() { + observer_->OnRefreshFinished(); BrowserThread::PostDelayedTask( BrowserThread::UI, FROM_HERE, base::Bind(&NativeDesktopMediaList::Refresh, diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 8862762ddabb..6dc971cd2066 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -7,13 +7,13 @@ screen and individual app windows. // In the renderer process. var desktopCapturer = require('desktop-capturer'); -desktopCapturer.on('source-added', function(source) { - console.log("source " + source.name + " is added."); - // source.thumbnail is not ready to use here and webkitGetUserMedia neither. +desktopCapturer.on('source-added', function(id, name) { + console.log("source " + name + " is added."); + // navigator.webkitGetUserMedia is not ready for use now. }); -desktopCapturer.on('source-thumbnail-changed', function(source) { - if (source.name == "Electron") { +desktopCapturer.on('source-thumbnail-changed', function(id, name, thumbnail) { + if (name == "Electron") { // stopUpdating since we have found the window that we want to capture. desktopCapturer.stopUpdating(); @@ -23,7 +23,7 @@ desktopCapturer.on('source-thumbnail-changed', function(source) { video: { mandatory: { chromeMediaSource: 'desktop', - chromeMediaSourceId: source.id, + chromeMediaSourceId: id, minWidth: 1280, maxWidth: 1280, minHeight: 720, @@ -50,42 +50,65 @@ function getUserMediaError(e) { ### Event: 'source-added' -* `source` Source +* `id` String - The id of the captured window or screen used in + `navigator.webkitGetUserMedia`. The format looks like 'window:XX' or + 'screen:XX' where XX is a random generated number. +* `name` String - The descriped name of the capturing screen or window. If the + source is a screen, the name will be 'Entire Screen' or 'Screen '; if + it is a window, the name will be the window's title. -Emits when there is a new source added, usually a new window is created, -a new screen is attached. +Emits when there is a new source added, usually a new window is created or a new +screen is attached. -**Note:** The thumbnail of the source is not ready for use when 'source-added' -event is emitted, and `navigator.webkitGetUserMedia` neither. +**Note:** `navigator.webkitGetUserMedia` is not ready for use in this event. ### Event: 'source-removed' -* `source` Source +* `id` String - The id of the captured window or screen used in + `navigator.webkitGetUserMedia`. +* `name` String - The descriped name of the capturing screen or window. Emits when there is a source removed. ### Event: 'source-name-changed' -* `source` Source +* `id` String - The id of the captured window or screen used in + `navigator.webkitGetUserMedia`. +* `name` String - The descriped name of the capturing screen or window. Emits when the name of source is changed. ### Event: 'source-thumbnail-changed' -* `source` Source +* `id` String - The id of the captured window or screen used in + `navigator.webkitGetUserMedia`. +* `name` String - The descriped name of the capturing screen or window. +* `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. Emits when the thumbnail of source is changed. `desktopCapturer` will refresh all sources every second. +### Event: 'refresh-finished' + +Emits when `desktopCapturer` finishes a refresh. + ## Methods The `desktopCapturer` module has the following methods: ### `desktopCapturer.startUpdating(options)` -* `options` Array - An array of String that enums the types of desktop sources. +`options` Object, properties: + +* `types` Array - An array of String that enums the types of desktop sources. * `screen` String - Screen * `window` String - Individual window +* `updatePeriod` Integer (optional) - The update period in milliseconds. By + default, `desktopCapturer` updates every second. +* `thumbnailWidth` Integer (optional) - The width of thumbnail. By default, it + is 150px. +* `thumbnailHeight` Integer (optional) - The height of thumbnail. By default, it + is 150px. Starts updating desktopCapturer. The events of `desktopCapturer` will only be emitted after `startUpdating` API is invoked. @@ -104,15 +127,5 @@ Stops updating desktopCapturer. The events of `desktopCapturer` will not be emitted after the API is invoked. **Note:** It is a good practice to call `stopUpdating` when you do not need -getting any infomation of sources, usually after passing a id of source to +getting any infomation of sources, usually after passing a id to `navigator.webkitGetUserMedia`. - -## Source - -`Source` is an object represents a captured screen or individual window. It has -following properties: - -* `id` String - The id of the capturing window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. -* `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. From dcb457e76ecc7ec530f9ececa5af2974ab5d17a5 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Tue, 6 Oct 2015 14:34:54 +0800 Subject: [PATCH 05/20] Refine API design: desktopCapturer.getSources. --- atom/browser/api/atom_api_desktop_capturer.cc | 69 ++++----- atom/browser/api/atom_api_desktop_capturer.h | 8 +- atom/browser/lib/desktop-capturer.coffee | 58 +++---- atom/renderer/api/lib/desktop-capturer.coffee | 21 +-- .../media/desktop_media_list_observer.h | 2 +- .../media/native_desktop_media_list.cc | 15 +- docs/api/desktop-capturer.md | 145 ++++++------------ 7 files changed, 124 insertions(+), 194 deletions(-) diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index a6556f6a89fa..116b0f4debf0 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -6,6 +6,7 @@ #include "atom/common/api/atom_api_native_image.h" #include "atom/common/node_includes.h" +#include "atom/common/native_mate_converters/gfx_converter.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/media/desktop_media_list.h" #include "native_mate/dictionary.h" @@ -14,13 +15,30 @@ #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 { -// Refresh every second. -const int kUpdatePeriod = 1000; const int kThumbnailWidth = 150; const int kThumbnailHeight = 150; } // namespace @@ -31,7 +49,7 @@ DesktopCapturer::DesktopCapturer() { DesktopCapturer::~DesktopCapturer() { } -void DesktopCapturer::StartUpdating(const mate::Dictionary& args) { +void DesktopCapturer::StartHandling(const mate::Dictionary& args) { std::vector sources; if (!args.Get("types", &sources)) return; @@ -68,64 +86,41 @@ void DesktopCapturer::StartUpdating(const mate::Dictionary& args) { media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); - int update_period = kUpdatePeriod; - int thumbnail_width = kThumbnailWidth, thumbnail_height = kThumbnailHeight; - args.Get("updatePeriod", &update_period); - args.Get("thumbnailWidth", &thumbnail_width); - args.Get("thumbnailHeight", &thumbnail_height); + gfx::Size thumbnail_size(kThumbnailWidth, kThumbnailHeight); + args.Get("thumbnailSize", &thumbnail_size); - media_list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds( - update_period)); - media_list_->SetThumbnailSize(gfx::Size(thumbnail_width, thumbnail_height)); + media_list_->SetThumbnailSize(thumbnail_size); media_list_->StartUpdating(this); } -void DesktopCapturer::StopUpdating() { - media_list_.reset(); -} - void DesktopCapturer::OnSourceAdded(int index) { - EmitDesktopCapturerEvent("source-added", index, false); } void DesktopCapturer::OnSourceRemoved(int index) { - EmitDesktopCapturerEvent("source-removed", index, false); } -// Ignore this event. void DesktopCapturer::OnSourceMoved(int old_index, int new_index) { } void DesktopCapturer::OnSourceNameChanged(int index) { - EmitDesktopCapturerEvent("source-removed", index, false); } void DesktopCapturer::OnSourceThumbnailChanged(int index) { - EmitDesktopCapturerEvent("source-thumbnail-changed", index, true); } -void DesktopCapturer::OnRefreshFinished() { - Emit("refresh-finished"); -} - -void DesktopCapturer::EmitDesktopCapturerEvent( - const std::string& event_name, int index, bool with_thumbnail) { - const DesktopMediaList::Source& source = media_list_->GetSource(index); - content::DesktopMediaID id = source.id; - if (!with_thumbnail) - Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name)); - else { - Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name), - atom::api::NativeImage::Create(isolate(), - gfx::Image(source.thumbnail))); - } +bool DesktopCapturer::OnRefreshFinished() { + std::vector sources; + for (int i = 0; i < media_list_->GetSourceCount(); ++i) + sources.push_back(media_list_->GetSource(i)); + media_list_.reset(); + Emit("refresh-finished", sources); + return false; } mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) - .SetMethod("startUpdating", &DesktopCapturer::StartUpdating) - .SetMethod("stopUpdating", &DesktopCapturer::StopUpdating); + .SetMethod("startHandling", &DesktopCapturer::StartHandling); } // static diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index f0620c8e9cf8..32687abf7367 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -27,9 +27,7 @@ class DesktopCapturer: public mate::EventEmitter, public: static mate::Handle Create(v8::Isolate* isolate); - void StartUpdating(const mate::Dictionary& args); - - void StopUpdating(); + void StartHandling(const mate::Dictionary& args); protected: DesktopCapturer(); @@ -41,11 +39,9 @@ class DesktopCapturer: public mate::EventEmitter, void OnSourceMoved(int old_index, int new_index) override; void OnSourceNameChanged(int index) override; void OnSourceThumbnailChanged(int index) override; - void OnRefreshFinished() override; + bool OnRefreshFinished() override; private: - void EmitDesktopCapturerEvent( - const std::string& event_name, int index, bool with_thumbnail); // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 0177d9721bb5..daef854cb74e 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -1,38 +1,38 @@ ipc = require 'ipc' -BrowserWindow = require 'browser-window' # The browser module manages all desktop-capturer moduels in renderer process. desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer -getWebContentsFromId = (id) -> - windows = BrowserWindow.getAllWindows() - return window.webContents for window in windows when window.webContents?.getId() == id +isOptionsEqual = (opt1, opt2) -> + return JSON.stringify opt1 is JSON.stringify opt2 -# The set for tracking id of webContents. -webContentsIds = new Set +# A queue for holding all requests from renderer process. +requestsQueue = [] -stopDesktopCapture = (id) -> - webContentsIds.delete id - # Stop updating if no renderer process listens the desktop capturer. - if webContentsIds.size is 0 - desktopCapturer.stopUpdating() +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options) -> + request = { options: options, webContents: event.sender } + desktopCapturer.startHandling options if requestsQueue.length is 0 + requestsQueue.push request + # If the WebContents is destroyed before receiving result, just remove the + # reference from requestsQueue to make the module not send the result to it. + event.sender.once 'destroyed', () -> + request.webContents = null -# Handle `desktopCapturer.startUpdating` API. -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', (event, args) -> - id = event.sender.getId() - if not webContentsIds.has id - # Stop sending desktop capturer events to the destroyed webContents. - event.sender.on 'destroyed', ()-> - stopDesktopCapture id - # Start updating the desktopCapturer if it doesn't. - if webContentsIds.size is 0 - desktopCapturer.startUpdating args - webContentsIds.add id +desktopCapturer.emit = (event_name, event, sources) -> + # Receiving sources result from main process, now send them back to renderer. + handledRequest = requestsQueue.shift 0 + result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) + handledRequest.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result -# Handle `desktopCapturer.stopUpdating` API. -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING', (event) -> - stopDesktopCapture event.sender.getId() - -desktopCapturer.emit = (event_name, event, desktopId, name, thumbnail) -> - webContentsIds.forEach (id) -> - getWebContentsFromId(id).send 'ATOM_RENDERER_DESKTOP_CAPTURER', event_name, desktopId, name, thumbnail?.toDataUrl() + # Check the queue to see whether there is other same request. If has, handle + # it for reducing redunplicated `desktopCaptuer.startHandling` calls. + unhandledRequestsQueue = [] + for request in requestsQueue + if isOptionsEqual handledRequest.options, request.options + request.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result + else + unhandledRequestsQueue.push request + requestsQueue = unhandledRequestsQueue + # If the requestsQueue is not empty, start a new request handling. + if requestsQueue.length > 0 + desktopCapturer.startHandling requestsQueue[0].options diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index ae5e054c2a75..7ac5a4024c9b 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -1,19 +1,10 @@ ipc = require 'ipc' -remote = require 'remote' NativeImage = require 'native-image' -EventEmitter = require('events').EventEmitter -desktopCapturer = new EventEmitter +getSources = (options, callback) -> + ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options + ipc.once 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', (sources) -> + callback ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) -desktopCapturer.startUpdating = (args) -> - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', args - -desktopCapturer.stopUpdating = () -> - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING' - -ipc.on 'ATOM_RENDERER_DESKTOP_CAPTURER', (event_name, id, name, thumbnail) -> - if not thumbnail - return desktopCapturer.emit event_name, id, name - desktopCapturer.emit event_name, id, name, NativeImage.createFromDataUrl thumbnail - -module.exports = desktopCapturer +module.exports = + getSources: getSources diff --git a/chromium_src/chrome/browser/media/desktop_media_list_observer.h b/chromium_src/chrome/browser/media/desktop_media_list_observer.h index eccaa407d2df..34cd626bf6bf 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list_observer.h +++ b/chromium_src/chrome/browser/media/desktop_media_list_observer.h @@ -14,7 +14,7 @@ class DesktopMediaListObserver { virtual void OnSourceMoved(int old_index, int new_index) = 0; virtual void OnSourceNameChanged(int index) = 0; virtual void OnSourceThumbnailChanged(int index) = 0; - virtual void OnRefreshFinished() = 0; + virtual bool OnRefreshFinished() = 0; protected: virtual ~DesktopMediaListObserver() {} diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index a326ded91b80..4d15b068b7c3 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -358,10 +358,13 @@ void NativeDesktopMediaList::OnSourceThumbnail( } void NativeDesktopMediaList::OnRefreshFinished() { - observer_->OnRefreshFinished(); - BrowserThread::PostDelayedTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&NativeDesktopMediaList::Refresh, - weak_factory_.GetWeakPtr()), - update_period_); + // Give a chance to the observer to stop the refresh work. + bool is_continue = observer_->OnRefreshFinished(); + if (is_continue) { + BrowserThread::PostDelayedTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::Refresh, + weak_factory_.GetWeakPtr()), + update_period_); + } } diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 6dc971cd2066..916538a4e9f8 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -7,36 +7,27 @@ screen and individual app windows. // In the renderer process. var desktopCapturer = require('desktop-capturer'); -desktopCapturer.on('source-added', function(id, name) { - console.log("source " + name + " is added."); - // navigator.webkitGetUserMedia is not ready for use now. -}); - -desktopCapturer.on('source-thumbnail-changed', function(id, name, thumbnail) { - if (name == "Electron") { - // stopUpdating since we have found the window that we want to capture. - desktopCapturer.stopUpdating(); - - // It's ready to use webkitGetUserMedia right now. - navigator.webkitGetUserMedia({ - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: id, - minWidth: 1280, - maxWidth: 1280, - minHeight: 720, - maxHeight: 720 +desktopCapturer.getSources({types: ['window', 'screen']}, function(sources) { + for (var i = 0; i < sources.length; ++i) { + if (sources[i].name == "Electron") { + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sources[i].id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } } - } - }, gotStream, getUserMediaError); + }, gotStream, getUserMediaError); + return; + } } }); -// Let's start updating after setting all event of `desktopCapturer` -desktopCapturer.startUpdating(); - function gotStream(stream) { document.querySelector('video').src = URL.createObjectURL(stream); } @@ -46,9 +37,35 @@ function getUserMediaError(e) { } ``` -## Events +## Methods -### Event: 'source-added' +The `desktopCapturer` module has the following methods: + +### `desktopCapturer.getSources(options, callback)` + +`options` Object, properties: +* `types` Array - An array of String that enums the types of desktop sources. + * `screen` String - Screen + * `window` String - Individual window +* `thumnailSize` Object (optional) - The suggested size that thumbnail should be + scaled. + * `width` Integer - The width of thumbnail. By default, it is 150px. + * `height` Integer - The height of thumbnail. By default, it is 150px. + +`callback` Function - `function(sources) {}` + +* `Sources` Array - An array of Source + +Gets all desktop sources. + +**Note:** There is no garuantee that the size of `source.thumbnail` is always +the same as the `thumnailSize` in `options`. It also depends on the scale of the +screen or window. + +## Source + +`Source` is an object represents a captured screen or individual window. It has +following properties: * `id` String - The id of the captured window or screen used in `navigator.webkitGetUserMedia`. The format looks like 'window:XX' or @@ -56,76 +73,4 @@ function getUserMediaError(e) { * `name` String - The descriped name of the capturing screen or window. If the source is a screen, the name will be 'Entire Screen' or 'Screen '; if it is a window, the name will be the window's title. - -Emits when there is a new source added, usually a new window is created or a new -screen is attached. - -**Note:** `navigator.webkitGetUserMedia` is not ready for use in this event. - -### Event: 'source-removed' - -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. - -Emits when there is a source removed. - -### Event: 'source-name-changed' - -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. - -Emits when the name of source is changed. - -### Event: 'source-thumbnail-changed' - -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. * `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. - -Emits when the thumbnail of source is changed. `desktopCapturer` will refresh -all sources every second. - -### Event: 'refresh-finished' - -Emits when `desktopCapturer` finishes a refresh. - -## Methods - -The `desktopCapturer` module has the following methods: - -### `desktopCapturer.startUpdating(options)` - -`options` Object, properties: - -* `types` Array - An array of String that enums the types of desktop sources. - * `screen` String - Screen - * `window` String - Individual window -* `updatePeriod` Integer (optional) - The update period in milliseconds. By - default, `desktopCapturer` updates every second. -* `thumbnailWidth` Integer (optional) - The width of thumbnail. By default, it - is 150px. -* `thumbnailHeight` Integer (optional) - The height of thumbnail. By default, it - is 150px. - -Starts updating desktopCapturer. The events of `desktopCapturer` will only be -emitted after `startUpdating` API is invoked. - -**Note:** At beginning, the desktopCapturer is initially empty, so the -`source-added` event will be emitted for each existing source as it is -enumrated. -On Windows, you will see the screen ficker when desktopCapturer starts updating. -This is normal because the desktop effects(e.g. Aero) will be disabled when -desktop capturer is working. The ficker will disappear once -`desktopCapturer.stopUpdating()` is invoked. - -### `desktopCapturer.stopUpdating()` - -Stops updating desktopCapturer. The events of `desktopCapturer` will not be -emitted after the API is invoked. - -**Note:** It is a good practice to call `stopUpdating` when you do not need -getting any infomation of sources, usually after passing a id to -`navigator.webkitGetUserMedia`. From 214f8477b32e7d4067742ed5b49a933dfe4dcffb Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sat, 17 Oct 2015 19:28:14 +0800 Subject: [PATCH 06/20] Fix some typos. --- atom/browser/lib/desktop-capturer.coffee | 4 ++-- atom/renderer/api/lib/desktop-capturer.coffee | 2 +- docs/api/desktop-capturer.md | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index daef854cb74e..fc3cdd5feaae 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -22,14 +22,14 @@ desktopCapturer.emit = (event_name, event, sources) -> # Receiving sources result from main process, now send them back to renderer. handledRequest = requestsQueue.shift 0 result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) - handledRequest.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result + handledRequest.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', result # Check the queue to see whether there is other same request. If has, handle # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue if isOptionsEqual handledRequest.options, request.options - request.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result + request.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', result else unhandledRequestsQueue.push request requestsQueue = unhandledRequestsQueue diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index 7ac5a4024c9b..a82eb69e657c 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -3,7 +3,7 @@ NativeImage = require 'native-image' getSources = (options, callback) -> ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options - ipc.once 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', (sources) -> + ipc.once 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', (sources) -> callback ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) module.exports = diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 916538a4e9f8..8002451c463b 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -47,8 +47,8 @@ The `desktopCapturer` module has the following methods: * `types` Array - An array of String that enums the types of desktop sources. * `screen` String - Screen * `window` String - Individual window -* `thumnailSize` Object (optional) - The suggested size that thumbnail should be - scaled. +* `thumbnailSize` Object (optional) - The suggested size that thumbnail should + be scaled. * `width` Integer - The width of thumbnail. By default, it is 150px. * `height` Integer - The height of thumbnail. By default, it is 150px. @@ -59,8 +59,8 @@ The `desktopCapturer` module has the following methods: Gets all desktop sources. **Note:** There is no garuantee that the size of `source.thumbnail` is always -the same as the `thumnailSize` in `options`. It also depends on the scale of the -screen or window. +the same as the `thumnbailSize` in `options`. It also depends on the scale of +the screen or window. ## Source From fb4efec55da118628263f21744a0ec8f6fce193a Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Mon, 19 Oct 2015 10:54:12 +0800 Subject: [PATCH 07/20] Add options check. This patch avoids main process never response back to renderer if the options is invalid. --- atom/browser/api/atom_api_desktop_capturer.cc | 37 ++++++++++++------- atom/browser/lib/desktop-capturer.coffee | 9 +++-- atom/renderer/api/lib/desktop-capturer.coffee | 5 ++- docs/api/desktop-capturer.md | 8 ++-- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index 116b0f4debf0..21e98fe47c21 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -41,6 +41,24 @@ namespace api { namespace { const int kThumbnailWidth = 150; const int kThumbnailHeight = 150; + +bool GetCapturerTypes(const mate::Dictionary& args, + bool* show_windows, + bool* show_screens) { + *show_windows = false; + *show_screens = false; + std::vector sources; + if (!args.Get("types", &sources)) + return false; + for (const auto& source_type : sources) { + if (source_type == "screen") + *show_screens = true; + else if (source_type == "window") + *show_windows = true; + } + return !show_windows && !show_screens ? false : true; +} + } // namespace DesktopCapturer::DesktopCapturer() { @@ -50,21 +68,14 @@ DesktopCapturer::~DesktopCapturer() { } void DesktopCapturer::StartHandling(const mate::Dictionary& args) { - std::vector sources; - if (!args.Get("types", &sources)) - return; - 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; - } - - if (!show_windows && !show_screens) + if (!GetCapturerTypes(args, &show_windows, &show_screens)) { + Emit("handling-finished", + "Invalid options.", + std::vector()); return; + } webrtc::DesktopCaptureOptions options = webrtc::DesktopCaptureOptions::CreateDefault(); @@ -113,7 +124,7 @@ bool DesktopCapturer::OnRefreshFinished() { for (int i = 0; i < media_list_->GetSourceCount(); ++i) sources.push_back(media_list_->GetSource(i)); media_list_.reset(); - Emit("refresh-finished", sources); + Emit("handling-finished", "", sources); return false; } diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index fc3cdd5feaae..916eda7b68aa 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -11,25 +11,26 @@ requestsQueue = [] ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options) -> request = { options: options, webContents: event.sender } - desktopCapturer.startHandling options if requestsQueue.length is 0 requestsQueue.push request + desktopCapturer.startHandling options if requestsQueue.length is 1 # If the WebContents is destroyed before receiving result, just remove the # reference from requestsQueue to make the module not send the result to it. event.sender.once 'destroyed', () -> request.webContents = null -desktopCapturer.emit = (event_name, event, sources) -> +desktopCapturer.emit = (event_name, event, error_message, sources) -> # Receiving sources result from main process, now send them back to renderer. handledRequest = requestsQueue.shift 0 + error = if error_message then Error error_message else null result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) - handledRequest.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', result + handledRequest.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', error_message, result # Check the queue to see whether there is other same request. If has, handle # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue if isOptionsEqual handledRequest.options, request.options - request.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', result + request.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', error_message, result else unhandledRequestsQueue.push request requestsQueue = unhandledRequestsQueue diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index a82eb69e657c..5c8caf076a3a 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -3,8 +3,9 @@ NativeImage = require 'native-image' getSources = (options, callback) -> ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options - ipc.once 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', (sources) -> - callback ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) + ipc.once 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', (error_message, sources) -> + error = if error_message then Error error_message else null + callback error, ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) module.exports = getSources: getSources diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 8002451c463b..2d805771cef0 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -7,7 +7,8 @@ screen and individual app windows. // In the renderer process. var desktopCapturer = require('desktop-capturer'); -desktopCapturer.getSources({types: ['window', 'screen']}, function(sources) { +desktopCapturer.getSources({types: ['window', 'screen']}, function(error, sources) { + if (error) throw error; for (var i = 0; i < sources.length; ++i) { if (sources[i].name == "Electron") { navigator.webkitGetUserMedia({ @@ -52,9 +53,10 @@ The `desktopCapturer` module has the following methods: * `width` Integer - The width of thumbnail. By default, it is 150px. * `height` Integer - The height of thumbnail. By default, it is 150px. -`callback` Function - `function(sources) {}` +`callback` Function - `function(error, sources) {}` -* `Sources` Array - An array of Source +* `error` Error +* `sources` Array - An array of Source Gets all desktop sources. From 9c861b9ad37a9c6335dde2e59d3005742fe75150 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Mon, 19 Oct 2015 18:07:35 +0800 Subject: [PATCH 08/20] Fix always passing the first result to renderer when the API is called multiple time at once. --- atom/browser/lib/desktop-capturer.coffee | 10 +++++----- atom/renderer/api/lib/desktop-capturer.coffee | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 916eda7b68aa..29d952595cb0 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -4,13 +4,13 @@ ipc = require 'ipc' desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer isOptionsEqual = (opt1, opt2) -> - return JSON.stringify opt1 is JSON.stringify opt2 + return JSON.stringify(opt1) is JSON.stringify(opt2) # A queue for holding all requests from renderer process. requestsQueue = [] -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options) -> - request = { options: options, webContents: event.sender } +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options, id) -> + request = { id: id, options: options, webContents: event.sender } requestsQueue.push request desktopCapturer.startHandling options if requestsQueue.length is 1 # If the WebContents is destroyed before receiving result, just remove the @@ -23,14 +23,14 @@ desktopCapturer.emit = (event_name, event, error_message, sources) -> handledRequest = requestsQueue.shift 0 error = if error_message then Error error_message else null result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) - handledRequest.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', error_message, result + handledRequest.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", error_message, result # Check the queue to see whether there is other same request. If has, handle # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue if isOptionsEqual handledRequest.options, request.options - request.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', error_message, result + request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", error_message, result else unhandledRequestsQueue.push request requestsQueue = unhandledRequestsQueue diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index 5c8caf076a3a..95e67ff99927 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -1,9 +1,13 @@ ipc = require 'ipc' NativeImage = require 'native-image' +nextId = 0 +getNextId = -> ++nextId + getSources = (options, callback) -> - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options - ipc.once 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', (error_message, sources) -> + id = getNextId() + ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, id + ipc.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (error_message, sources) -> error = if error_message then Error error_message else null callback error, ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) From d458b24945c4353c22543b92bdddd01bbc122f21 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 12:44:55 +0800 Subject: [PATCH 09/20] Add desktopCapturer to electron --- atom/renderer/api/lib/exports/electron.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/atom/renderer/api/lib/exports/electron.coffee b/atom/renderer/api/lib/exports/electron.coffee index 5d7f2a57edd0..a224818e5ffa 100644 --- a/atom/renderer/api/lib/exports/electron.coffee +++ b/atom/renderer/api/lib/exports/electron.coffee @@ -3,6 +3,9 @@ module.exports = require '../../../../common/api/lib/exports/electron' Object.defineProperties module.exports, # Renderer side modules, please sort with alphabet order. + desktopCapturer: + enumerable: true + get: -> require '../desktop-capturer' ipcRenderer: enumerable: true get: -> require '../ipc-renderer' From 785bc2986b0752d40bd3b51108e6d1f1d0bc99ca Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 12:47:07 +0800 Subject: [PATCH 10/20] Update brightray and libchromium for desktopCapturer --- script/lib/config.py | 2 +- vendor/brightray | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index 159839c7f559..3e7f23536c9b 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '451ea93cc3090f7000f8f0daa4cb84e90ad6c842' +LIBCHROMIUMCONTENT_COMMIT = '080aeb05f3ef869acc4234dbcc8a815e73047e2b' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index 9b4d052d2af7..8c8c3dbade86 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 9b4d052d2af716c340034ed7815d9d758cd7804d +Subproject commit 8c8c3dbade86d19de28bc750e7b3f79668a85613 From 51368952a201ab41863ec3ac1c77e63d1b518688 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 12:58:39 +0800 Subject: [PATCH 11/20] Remove deprecated API usages --- atom/browser/lib/desktop-capturer.coffee | 12 +++++------- atom/renderer/api/lib/desktop-capturer.coffee | 12 ++++-------- .../chrome/browser/media/desktop_media_list.h | 1 + .../browser/media/native_desktop_media_list.cc | 1 + 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 29d952595cb0..8ef3389f5f1a 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -1,15 +1,13 @@ -ipc = require 'ipc' +{ipcMain} = require 'electron' +{desktopCapturer} = process.atomBinding 'desktop_capturer' -# The browser module manages all desktop-capturer moduels in renderer process. -desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer - -isOptionsEqual = (opt1, opt2) -> +deepEqual = (opt1, opt2) -> return JSON.stringify(opt1) is JSON.stringify(opt2) # A queue for holding all requests from renderer process. requestsQueue = [] -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options, id) -> +ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options, id) -> request = { id: id, options: options, webContents: event.sender } requestsQueue.push request desktopCapturer.startHandling options if requestsQueue.length is 1 @@ -29,7 +27,7 @@ desktopCapturer.emit = (event_name, event, error_message, sources) -> # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue - if isOptionsEqual handledRequest.options, request.options + if deepEqual handledRequest.options, request.options request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", error_message, result else unhandledRequestsQueue.push request diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index 95e67ff99927..fc4118f067b3 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -1,15 +1,11 @@ -ipc = require 'ipc' -NativeImage = require 'native-image' +{ipcRenderer, NativeImage} = require 'electron' nextId = 0 getNextId = -> ++nextId -getSources = (options, callback) -> +exports.getSources = (options, callback) -> id = getNextId() - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, id - ipc.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (error_message, sources) -> + ipcRenderer.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, id + ipcRenderer.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (error_message, sources) -> error = if error_message then Error error_message else null callback error, ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) - -module.exports = - getSources: getSources diff --git a/chromium_src/chrome/browser/media/desktop_media_list.h b/chromium_src/chrome/browser/media/desktop_media_list.h index fa0dbd815749..44850bd69121 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list.h +++ b/chromium_src/chrome/browser/media/desktop_media_list.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ #include "base/basictypes.h" +#include "base/strings/string16.h" #include "base/time/time.h" #include "content/public/browser/desktop_media_id.h" #include "ui/gfx/image/image_skia.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 index 4d15b068b7c3..2464aeca4931 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -10,6 +10,7 @@ #include "base/hash.h" #include "base/logging.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/sequenced_worker_pool.h" #include "chrome/browser/media/desktop_media_list_observer.h" From b517b0c59843d6ab1c66789dcc17f01b7c6673cc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 13:09:36 +0800 Subject: [PATCH 12/20] docs: Improve docs of desktopCapturer --- docs/api/desktop-capturer.md | 53 +++++++++++++++--------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 2d805771cef0..e0ec75842841 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -1,11 +1,11 @@ -# desktop-capturer +# desktopCapturer -The `desktop-capturer` is a renderer module used to capture the content of -screen and individual app windows. +The `desktopCapturer` module can be used to get available sources that can be +used to be captured with `getUserMedia`. ```javascript // In the renderer process. -var desktopCapturer = require('desktop-capturer'); +var desktopCapturer = require('electron').desktopCapturer; desktopCapturer.getSources({types: ['window', 'screen']}, function(error, sources) { if (error) throw error; @@ -44,35 +44,26 @@ The `desktopCapturer` module has the following methods: ### `desktopCapturer.getSources(options, callback)` -`options` Object, properties: -* `types` Array - An array of String that enums the types of desktop sources. - * `screen` String - Screen - * `window` String - Individual window -* `thumbnailSize` Object (optional) - The suggested size that thumbnail should - be scaled. - * `width` Integer - The width of thumbnail. By default, it is 150px. - * `height` Integer - The height of thumbnail. By default, it is 150px. +* `options` Object + * `types` Array - An array of String that lists the types of desktop sources + to be captured, available types are `screen` and `window`. + * `thumbnailSize` Object (optional) - The suggested size that thumbnail should + be scaled, it is `{width: 150, height: 150}` by default. +* `callback` Function -`callback` Function - `function(error, sources) {}` - -* `error` Error -* `sources` Array - An array of Source - -Gets all desktop sources. - -**Note:** There is no garuantee that the size of `source.thumbnail` is always -the same as the `thumnbailSize` in `options`. It also depends on the scale of -the screen or window. - -## Source - -`Source` is an object represents a captured screen or individual window. It has -following properties: +Starts a request to get all desktop sources, `callback` will be called with +`callback(error, sources)` when the request is completed. +The `sources` is an array of `Source` objects, each `Source` represents a +captured screen or individual window, and has following properties: * `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. The format looks like 'window:XX' or - 'screen:XX' where XX is a random generated number. -* `name` String - The descriped name of the capturing screen or window. If the - source is a screen, the name will be 'Entire Screen' or 'Screen '; if + `navigator.webkitGetUserMedia`. The format looks like `window:XX` or + `screen:XX` where `XX` is a random generated number. +* `name` String - The described name of the capturing screen or window. If the + source is a screen, the name will be `Entire Screen` or `Screen `; if it is a window, the name will be the window's title. * `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. + +**Note:** There is no guarantee that the size of `source.thumbnail` is always +the same as the `thumnbailSize` in `options`. It also depends on the scale of +the screen or window. From f6c9000f5f33601e333b1c0e73f136e3b3de571f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 13:49:11 +0800 Subject: [PATCH 13/20] spec: Add a simple test case for desktopCapturer --- spec/api-desktop-capturer.coffee | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 spec/api-desktop-capturer.coffee diff --git a/spec/api-desktop-capturer.coffee b/spec/api-desktop-capturer.coffee new file mode 100644 index 000000000000..6f1842dd145c --- /dev/null +++ b/spec/api-desktop-capturer.coffee @@ -0,0 +1,9 @@ +assert = require 'assert' +{desktopCapturer} = require 'electron' + +describe 'desktopCapturer', -> + it 'should returns something', (done) -> + desktopCapturer.getSources {types: ['window', 'screen']}, (error, sources) -> + assert.equal error, null + assert.notEqual sources.length, 0 + done() From 836a8b179460a934c5bbb75baa0ef66c83def001 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 13:49:26 +0800 Subject: [PATCH 14/20] Simplify the desktopCapturer code --- atom/browser/api/atom_api_desktop_capturer.cc | 49 +++---------------- atom/browser/api/atom_api_desktop_capturer.h | 12 ++--- atom/browser/lib/desktop-capturer.coffee | 18 +++---- atom/renderer/api/lib/desktop-capturer.coffee | 19 +++++-- .../chrome/browser/media/desktop_media_list.h | 1 + .../media/native_desktop_media_list.cc | 4 ++ .../browser/media/native_desktop_media_list.h | 1 + 7 files changed, 38 insertions(+), 66 deletions(-) diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index 21e98fe47c21..ceb69deca452 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -10,7 +10,6 @@ #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" @@ -38,45 +37,15 @@ namespace atom { namespace api { -namespace { -const int kThumbnailWidth = 150; -const int kThumbnailHeight = 150; - -bool GetCapturerTypes(const mate::Dictionary& args, - bool* show_windows, - bool* show_screens) { - *show_windows = false; - *show_screens = false; - std::vector sources; - if (!args.Get("types", &sources)) - return false; - for (const auto& source_type : sources) { - if (source_type == "screen") - *show_screens = true; - else if (source_type == "window") - *show_windows = true; - } - return !show_windows && !show_screens ? false : true; -} - -} // namespace - DesktopCapturer::DesktopCapturer() { } DesktopCapturer::~DesktopCapturer() { } -void DesktopCapturer::StartHandling(const mate::Dictionary& args) { - bool show_screens = false; - bool show_windows = false; - if (!GetCapturerTypes(args, &show_windows, &show_screens)) { - Emit("handling-finished", - "Invalid options.", - std::vector()); - return; - } - +void DesktopCapturer::StartHandling(bool capture_window, + bool capture_screen, + const gfx::Size& thumbnail_size) { webrtc::DesktopCaptureOptions options = webrtc::DesktopCaptureOptions::CreateDefault(); @@ -91,15 +60,12 @@ void DesktopCapturer::StartHandling(const mate::Dictionary& args) { #endif scoped_ptr screen_capturer( - show_screens ? webrtc::ScreenCapturer::Create(options) : nullptr); + capture_screen ? webrtc::ScreenCapturer::Create(options) : nullptr); scoped_ptr window_capturer( - show_windows ? webrtc::WindowCapturer::Create(options) : nullptr); + capture_window ? webrtc::WindowCapturer::Create(options) : nullptr); media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); - gfx::Size thumbnail_size(kThumbnailWidth, kThumbnailHeight); - args.Get("thumbnailSize", &thumbnail_size); - media_list_->SetThumbnailSize(thumbnail_size); media_list_->StartUpdating(this); } @@ -120,11 +86,8 @@ void DesktopCapturer::OnSourceThumbnailChanged(int index) { } bool DesktopCapturer::OnRefreshFinished() { - std::vector sources; - for (int i = 0; i < media_list_->GetSourceCount(); ++i) - sources.push_back(media_list_->GetSource(i)); + Emit("finished", media_list_->GetSources()); media_list_.reset(); - Emit("handling-finished", "", sources); return false; } diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index 32687abf7367..c22c8a44835f 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -5,19 +5,11 @@ #ifndef ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ #define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ -#include -#include - -#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 mate { -class Dictionary; -} - namespace atom { namespace api { @@ -27,7 +19,9 @@ class DesktopCapturer: public mate::EventEmitter, public: static mate::Handle Create(v8::Isolate* isolate); - void StartHandling(const mate::Dictionary& args); + void StartHandling(bool capture_window, + bool capture_screen, + const gfx::Size& thumbnail_size); protected: DesktopCapturer(); diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 8ef3389f5f1a..a7fb29ff76ed 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -7,31 +7,31 @@ deepEqual = (opt1, opt2) -> # A queue for holding all requests from renderer process. requestsQueue = [] -ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options, id) -> - request = { id: id, options: options, webContents: event.sender } +ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, captureWindow, captureScreen, thumbnailSize, id) -> + request = id: id, options: {captureWindow, captureScreen, thumbnailSize}, webContents: event.sender requestsQueue.push request - desktopCapturer.startHandling options if requestsQueue.length is 1 + desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize if requestsQueue.length is 1 # If the WebContents is destroyed before receiving result, just remove the # reference from requestsQueue to make the module not send the result to it. - event.sender.once 'destroyed', () -> + event.sender.once 'destroyed', -> request.webContents = null -desktopCapturer.emit = (event_name, event, error_message, sources) -> +desktopCapturer.emit = (event, name, sources) -> # Receiving sources result from main process, now send them back to renderer. handledRequest = requestsQueue.shift 0 - error = if error_message then Error error_message else null result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) - handledRequest.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", error_message, result + handledRequest.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", result # Check the queue to see whether there is other same request. If has, handle # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue if deepEqual handledRequest.options, request.options - request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", error_message, result + request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", errorMessage, result else unhandledRequestsQueue.push request requestsQueue = unhandledRequestsQueue # If the requestsQueue is not empty, start a new request handling. if requestsQueue.length > 0 - desktopCapturer.startHandling requestsQueue[0].options + {captureWindow, captureScreen, thumbnailSize} = requestsQueue[0].options + desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index fc4118f067b3..7c7c8982491f 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -1,11 +1,20 @@ -{ipcRenderer, NativeImage} = require 'electron' +{ipcRenderer, nativeImage} = require 'electron' nextId = 0 getNextId = -> ++nextId +# |options.type| can not be empty and has to include 'window' or 'screen'. +isValid = (options) -> + return options?.types? and Array.isArray options.types + exports.getSources = (options, callback) -> + return callback new Error('Invalid options') unless isValid options + + captureWindow = 'window' in options.types + captureScreen = 'screen' in options.types + options.thumbnailSize ?= width: 150, height: 150 + id = getNextId() - ipcRenderer.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, id - ipcRenderer.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (error_message, sources) -> - error = if error_message then Error error_message else null - callback error, ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) + ipcRenderer.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id + ipcRenderer.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (event, sources) -> + callback null, ({id: source.id, name: source.name, thumbnail: nativeImage.createFromDataURL source.thumbnail} for source in sources) diff --git a/chromium_src/chrome/browser/media/desktop_media_list.h b/chromium_src/chrome/browser/media/desktop_media_list.h index 44850bd69121..7ef703e8b7b3 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list.h +++ b/chromium_src/chrome/browser/media/desktop_media_list.h @@ -56,6 +56,7 @@ class DesktopMediaList { virtual int GetSourceCount() const = 0; virtual const Source& GetSource(int index) const = 0; + virtual std::vector GetSources() const = 0; }; #endif // CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_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 index 2464aeca4931..4a7fb58962c6 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -282,6 +282,10 @@ const DesktopMediaList::Source& NativeDesktopMediaList::GetSource( return sources_[index]; } +std::vector NativeDesktopMediaList::GetSources() const { + return sources_; +} + void NativeDesktopMediaList::Refresh() { capture_task_runner_->PostTask( FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()), diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.h b/chromium_src/chrome/browser/media/native_desktop_media_list.h index 81bd32152813..943d3dd32565 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.h +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.h @@ -36,6 +36,7 @@ class NativeDesktopMediaList : public DesktopMediaList { void StartUpdating(DesktopMediaListObserver* observer) override; int GetSourceCount() const override; const Source& GetSource(int index) const override; + std::vector GetSources() const override; void SetViewDialogWindowId(content::DesktopMediaID::Id dialog_id) override; private: From 9eb56272257d63b0934e0dbf8e1d3fa4c5ed5b33 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 14:21:23 +0800 Subject: [PATCH 15/20] Update clang to the same version with Chrome 47 --- script/update-clang.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/update-clang.sh b/script/update-clang.sh index 5f9b00da7d9a..801d60397145 100755 --- a/script/update-clang.sh +++ b/script/update-clang.sh @@ -8,7 +8,7 @@ # Do NOT CHANGE this if you don't know what you're doing -- see # https://code.google.com/p/chromium/wiki/UpdatingClang # Reverting problematic clang rolls is safe, though. -CLANG_REVISION=245965 +CLANG_REVISION=247874 # This is incremented when pushing a new build of Clang at the same revision. CLANG_SUB_REVISION=1 From 98169032fd2ed336fdd9f685fe751eff38ad3b51 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 14:49:13 +0800 Subject: [PATCH 16/20] Update libchromiumcontent and brightray --- script/lib/config.py | 4 ++-- vendor/brightray | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index 3e7f23536c9b..acb4c2e970ff 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -7,8 +7,8 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ - 'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '080aeb05f3ef869acc4234dbcc8a815e73047e2b' + 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent' +LIBCHROMIUMCONTENT_COMMIT = 'd4b964338cb1cf81a59cd8aeec432f9e6fded802' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index 8c8c3dbade86..476b9fc55058 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 8c8c3dbade86d19de28bc750e7b3f79668a85613 +Subproject commit 476b9fc55058a9528f6f016206f45564ee0113d9 From 640b4c3c666e27a866956ea09fd586a4343f2d6f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 16:56:20 +0800 Subject: [PATCH 17/20] Update brightray for various linking problems --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 476b9fc55058..c5cac4882b3a 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 476b9fc55058a9528f6f016206f45564ee0113d9 +Subproject commit c5cac4882b3a5d991ad87fa410ccd754bafe5e53 From 9548a3801b0ebcda1fcee682261e571a60f7a1af Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 17:03:10 +0800 Subject: [PATCH 18/20] Update brightray: Link a few more X libraries --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index c5cac4882b3a..a2a2f0b3c325 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit c5cac4882b3a5d991ad87fa410ccd754bafe5e53 +Subproject commit a2a2f0b3c325c112bab86682d0e78d64d5988413 From 9daeafbace0acd7fbd02d5a9bb7feed4ffb33069 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 17:32:42 +0800 Subject: [PATCH 19/20] Install libxtst-dev on travis ci --- script/cibuild | 1 + 1 file changed, 1 insertion(+) diff --git a/script/cibuild b/script/cibuild index 08d59385c9e0..c0798dc7e2c3 100755 --- a/script/cibuild +++ b/script/cibuild @@ -17,6 +17,7 @@ LINUX_DEPS = [ 'libgtk2.0-dev', 'libnotify-dev', 'libnss3-dev', + 'libxtst-dev', 'gcc-multilib', 'g++-multilib', ] From 505193e239428d9dc790dd0a0de9d4faccff0b6e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 18:15:01 +0800 Subject: [PATCH 20/20] Link with libyuv_neon.a on ARM --- script/lib/config.py | 2 +- vendor/brightray | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index acb4c2e970ff..c1545e0d27d8 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = 'd4b964338cb1cf81a59cd8aeec432f9e6fded802' +LIBCHROMIUMCONTENT_COMMIT = 'cfbe8ec7e14af4cabd1474386f54e197db1f7ac1' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index a2a2f0b3c325..814923b77d21 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit a2a2f0b3c325c112bab86682d0e78d64d5988413 +Subproject commit 814923b77d21261a9d1780bff0bd1beb55203843