diff options
| author | reveman <reveman@chromium.org> | 2015-11-19 14:16:48 -0800 |
|---|---|---|
| committer | Commit bot <commit-bot@chromium.org> | 2015-11-19 22:17:34 +0000 |
| commit | b195f41daf755c80d31f964538b1159c81d58e70 (patch) | |
| tree | 43eb3395abffda3cf49cc9d0b376a85d545ece74 | |
| parent | 96bac60134c383aa929ce6139c168bd31e87f67d (diff) | |
| download | chromium_src-b195f41daf755c80d31f964538b1159c81d58e70.zip chromium_src-b195f41daf755c80d31f964538b1159c81d58e70.tar.gz chromium_src-b195f41daf755c80d31f964538b1159c81d58e70.tar.bz2 | |
components: Add Exosphere component.
Exosphere is a set of display server classes for Chromium that
can be used for building a display server on top of Ash, the
Chrome Compositor and the GpuMemoryBuffer framework.
The classes and interfaces are inspired by the Wayland protocol
and reference implementation of a display server that uses
the Wayland protocol and server library is provided in the
wayland/ sub-directory.
BUG=549781
TEST=components_unittests --gtest_filter=ServerTest.*:SurfaceTest.*:BufferTest.*:DisplayTest.*:ShellSurfaceTest.*:SharedMemoryTest.*
Review URL: https://codereview.chromium.org/1412093006
Cr-Commit-Position: refs/heads/master@{#360672}
49 files changed, 2637 insertions, 1 deletions
diff --git a/ash/BUILD.gn b/ash/BUILD.gn index deb3226..067e045 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn @@ -196,6 +196,10 @@ source_set("test_support") { if (is_win) { deps += [ "//ui/platform_window/win" ] } + + if (is_chromeos) { + deps += [ "//ui/display" ] + } } source_set("test_support_with_content") { diff --git a/build/common.gypi b/build/common.gypi index ca65fc3..dcdf155 100644 --- a/build/common.gypi +++ b/build/common.gypi @@ -85,6 +85,9 @@ # Enable top chrome material design. 'enable_topchrome_md%' : 0, + # Enable Wayland display server support. + 'enable_wayland_server%' : 0, + # Build against pre-built sysroot image on linux. By default # the sysroot image is only used for Official builds or when cross # compiling. @@ -161,6 +164,7 @@ 'enable_viewport%': '<(enable_viewport)', 'enable_hidpi%': '<(enable_hidpi)', 'enable_topchrome_md%': '<(enable_topchrome_md)', + 'enable_wayland_server%': '<(enable_wayland_server)', 'buildtype%': '<(buildtype)', 'branding%': '<(branding)', 'branding_path_component%': '<(branding)', @@ -337,6 +341,7 @@ 'enable_viewport%': '<(enable_viewport)', 'enable_hidpi%': '<(enable_hidpi)', 'enable_topchrome_md%': '<(enable_topchrome_md)', + 'enable_wayland_server%': '<(enable_wayland_server)', 'android_channel%': '<(android_channel)', 'use_goma%': '<(use_goma)', 'gomadir%': '<(gomadir)', @@ -751,6 +756,13 @@ 'use_glib%': 1, }], + # Flags to use Wayland server support. + ['chromeos==1 and use_ozone==1', { + 'enable_wayland_server%': 1, + }, { + 'enable_wayland_server%': 0, + }], + # Flags to use pango and cairo. ['OS=="win" or OS=="mac" or OS=="ios" or OS=="android" or embedded==1', { 'use_pango%': 0, @@ -1128,6 +1140,7 @@ 'enable_viewport%': '<(enable_viewport)', 'enable_hidpi%': '<(enable_hidpi)', 'enable_topchrome_md%': '<(enable_topchrome_md)', + 'enable_wayland_server%': '<(enable_wayland_server)', 'image_loader_extension%': '<(image_loader_extension)', 'fastbuild%': '<(fastbuild)', 'dont_embed_build_metadata%': '<(dont_embed_build_metadata)', @@ -2757,6 +2770,9 @@ ['enable_topchrome_md==1', { 'defines': ['ENABLE_TOPCHROME_MD=1'], }], + ['enable_wayland_server==1', { + 'defines': ['ENABLE_WAYLAND_SERVER=1'], + }], ['use_udev==1', { 'defines': ['USE_UDEV'], }], diff --git a/build/config/BUILD.gn b/build/config/BUILD.gn index bfb0de7..643d4d9 100644 --- a/build/config/BUILD.gn +++ b/build/config/BUILD.gn @@ -230,6 +230,9 @@ config("feature_flags") { if (enable_topchrome_md) { defines += [ "ENABLE_TOPCHROME_MD=1" ] } + if (enable_wayland_server) { + defines += [ "ENABLE_WAYLAND_SERVER=1" ] + } if (proprietary_codecs) { defines += [ "USE_PROPRIETARY_CODECS" ] } diff --git a/build/config/ui.gni b/build/config/ui.gni index 3acf655..fa4f142 100644 --- a/build/config/ui.gni +++ b/build/config/ui.gni @@ -46,6 +46,9 @@ declare_args() { # Whether we should use glib, a low level C utility library. use_glib = is_linux + + # Indicates if Wayland display server support is enabled. + enable_wayland_server = is_chromeos } # Additional dependent variables ----------------------------------------------- @@ -66,6 +69,11 @@ if (use_ozone) { use_glib = false } +# Turn off Wayland if glib is enabled. +if (use_glib) { + enable_wayland_server = false +} + if (is_linux && !use_ozone) { use_cairo = true use_pango = true diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 2a0fcbe..e1d05f9 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn @@ -901,6 +901,17 @@ source_set("browser") { if (enable_mojo_media == "utility") { deps += [ "//media/mojo/services:application" ] } + + if (enable_wayland_server) { + deps += [ + "//components/exo", + "//components/exo/wayland", + ] + sources += [ + "chrome_browser_main_extra_parts_exo.cc", + "chrome_browser_main_extra_parts_exo.h", + ] + } } if (is_android) { diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS index 1a49bfb..27c939d 100644 --- a/chrome/browser/DEPS +++ b/chrome/browser/DEPS @@ -41,6 +41,7 @@ include_rules = [ "+components/dom_distiller", "+components/domain_reliability", "+components/enhanced_bookmarks", + "+components/exo", "+components/favicon", "+components/favicon_base", "+components/feedback", diff --git a/chrome/browser/chrome_browser_main_extra_parts_exo.cc b/chrome/browser/chrome_browser_main_extra_parts_exo.cc new file mode 100644 index 0000000..9cecfbd --- /dev/null +++ b/chrome/browser/chrome_browser_main_extra_parts_exo.cc @@ -0,0 +1,59 @@ +// Copyright 2015 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/chrome_browser_main_extra_parts_exo.h" + +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "chrome/browser/ui/ash/ash_util.h" +#include "chrome/common/chrome_switches.h" +#include "components/exo/display.h" +#include "components/exo/wayland/server.h" +#include "content/public/browser/browser_thread.h" + +class ChromeBrowserMainExtraPartsExo::WaylandWatcher + : public base::MessagePumpLibevent::Watcher { + public: + explicit WaylandWatcher(exo::wayland::Server* server) : server_(server) { + base::MessageLoopForUI::current()->WatchFileDescriptor( + server_->GetFileDescriptor(), + true, // persistent + base::MessagePumpLibevent::WATCH_READ, &controller_, this); + } + + // base::MessagePumpLibevent::Watcher: + void OnFileCanReadWithoutBlocking(int fd) override { + server_->Dispatch(base::TimeDelta()); + server_->Flush(); + } + void OnFileCanWriteWithoutBlocking(int fd) override { NOTREACHED(); } + + private: + base::MessagePumpLibevent::FileDescriptorWatcher controller_; + exo::wayland::Server* const server_; + + DISALLOW_COPY_AND_ASSIGN(WaylandWatcher); +}; + +ChromeBrowserMainExtraPartsExo::ChromeBrowserMainExtraPartsExo() + : display_(new exo::Display) {} + +ChromeBrowserMainExtraPartsExo::~ChromeBrowserMainExtraPartsExo() {} + +void ChromeBrowserMainExtraPartsExo::PreProfileInit() { + if (!chrome::ShouldOpenAshOnStartup()) + return; + + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableWaylandServer)) { + wayland_server_ = exo::wayland::Server::Create(display_.get()); + wayland_watcher_ = + make_scoped_ptr(new WaylandWatcher(wayland_server_.get())); + } +} + +void ChromeBrowserMainExtraPartsExo::PostMainMessageLoopRun() { + wayland_watcher_.reset(); + wayland_server_.reset(); +} diff --git a/chrome/browser/chrome_browser_main_extra_parts_exo.h b/chrome/browser/chrome_browser_main_extra_parts_exo.h new file mode 100644 index 0000000..16960e9 --- /dev/null +++ b/chrome/browser/chrome_browser_main_extra_parts_exo.h @@ -0,0 +1,37 @@ +// Copyright 2015 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_CHROME_BROWSER_MAIN_EXTRA_PARTS_EXO_H_ +#define CHROME_BROWSER_CHROME_BROWSER_MAIN_EXTRA_PARTS_EXO_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/chrome_browser_main_extra_parts.h" + +namespace exo { +class Display; +namespace wayland { +class Server; +} +} + +class ChromeBrowserMainExtraPartsExo : public ChromeBrowserMainExtraParts { + public: + ChromeBrowserMainExtraPartsExo(); + ~ChromeBrowserMainExtraPartsExo() override; + + // Overridden from ChromeBrowserMainExtraParts: + void PreProfileInit() override; + void PostMainMessageLoopRun() override; + + private: + scoped_ptr<exo::Display> display_; + scoped_ptr<exo::wayland::Server> wayland_server_; + class WaylandWatcher; + scoped_ptr<WaylandWatcher> wayland_watcher_; + + DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainExtraPartsExo); +}; + +#endif // CHROME_BROWSER_CHROME_BROWSER_MAIN_EXTRA_PARTS_EXO_H_ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index fc1d416..2956d6e 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -276,6 +276,10 @@ #include "chrome/browser/media/router/presentation_service_delegate_impl.h" #endif +#if defined(ENABLE_WAYLAND_SERVER) +#include "chrome/browser/chrome_browser_main_extra_parts_exo.h" +#endif + using base::FileDescriptor; using blink::WebWindowFeatures; using content::AccessTokenStore; @@ -753,6 +757,10 @@ content::BrowserMainParts* ChromeContentBrowserClient::CreateBrowserMainParts( main_parts->AddParts(new ChromeBrowserMainExtraPartsX11()); #endif +#if defined(ENABLE_WAYLAND_SERVER) + main_parts->AddParts(new ChromeBrowserMainExtraPartsExo()); +#endif + chrome::AddMetricsExtraParts(main_parts); return main_parts; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 96acbc1..e4018c2 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -3871,6 +3871,16 @@ '../components/components.gyp:printing_browser', ] }], + ['enable_wayland_server==1', { + 'sources': [ + 'browser/chrome_browser_main_extra_parts_exo.cc', + 'browser/chrome_browser_main_extra_parts_exo.h', + ], + 'dependencies': [ + '../components/components.gyp:exo', + '../components/components.gyp:exo_wayland', + ], + }], ], }, { diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index c02bacc..0e8e595 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -1309,6 +1309,11 @@ const char kDebugPrint[] = "debug-print"; const char kDisableNewTaskManager[] = "disable-new-task-manager"; #endif // defined(ENABLE_TASK_MANAGER) +#if defined(ENABLE_WAYLAND_SERVER) +// Enables Wayland display server support. +const char kEnableWaylandServer[] = "enable-wayland-server"; +#endif + #if defined(OS_ANDROID) // Sets the threshold for when to disable auto-hiding the toolbar. If the // device's width and height are above the threshold, the toolbar will never diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 4dbc24a..c66a8e9 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -393,6 +393,10 @@ extern const char kDisallowUncheckedDangerousDownloads[]; extern const char kDisableNewTaskManager[]; #endif // defined(ENABLE_TASK_MANAGER) +#if defined(ENABLE_WAYLAND_SERVER) +extern const char kEnableWaylandServer[]; +#endif + bool AboutInSettingsEnabled(); bool MdExtensionsEnabled(); bool MdPolicyPageEnabled(); diff --git a/components/BUILD.gn b/components/BUILD.gn index f0cac84..33fbe66 100644 --- a/components/BUILD.gn +++ b/components/BUILD.gn @@ -115,6 +115,8 @@ group("all_components") { "//components/devtools_discovery", "//components/devtools_http_handler", "//components/domain_reliability", + "//components/exo", + "//components/exo/wayland", "//components/feedback", "//components/gcm_driver/crypto", "//components/gcm_driver/instance_id", @@ -230,6 +232,14 @@ group("all_components") { if (is_chromeos && enable_arc) { deps += [ "//components/arc" ] } + + if (!use_ash) { + deps -= [ "//components/exo" ] + } + + if (!use_ash || !is_linux) { + deps -= [ "//components/exo/wayland" ] + } } else { deps += [ "//components/autofill/ios/browser", diff --git a/components/OWNERS b/components/OWNERS index da54de3..c507fb6 100644 --- a/components/OWNERS +++ b/components/OWNERS @@ -115,6 +115,8 @@ per-file enhanced_bookmarks*=sky@chromium.org per-file error_page*=mmenke@chromium.org per-file error_page*=ttuttle@chromium.org +per-file exo.gypi=reveman@chromium.org + per-file external_video_surface.gypi=boliu@chromium.org per-file external_video_surface.gypi=qinmin@chromium.org diff --git a/components/components.gyp b/components/components.gyp index b98cd63..2729f9a 100644 --- a/components/components.gyp +++ b/components/components.gyp @@ -201,6 +201,11 @@ 'includes': [ 'rlz.gypi', ], - }] + }], + ['use_ash==1', { + 'includes': [ + 'exo.gypi', + ], + }], ], } diff --git a/components/exo.gypi b/components/exo.gypi new file mode 100644 index 0000000..d4c762b --- /dev/null +++ b/components/exo.gypi @@ -0,0 +1,70 @@ +# Copyright 2015 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. + +{ + 'targets': [ + { + # GN version: //components/exo + 'target_name': 'exo', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../ash/ash.gyp:ash', + '../base/base.gyp:base', + '../cc/cc.gyp:cc', + '../gpu/gpu.gyp:gpu', + '../skia/skia.gyp:skia', + '../ui/aura/aura.gyp:aura', + '../ui/compositor/compositor.gyp:compositor', + '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', + '../ui/gl/gl.gyp:gl', + '../ui/views/views.gyp:views', + ], + 'sources': [ + # Note: sources list duplicated in GN build. + 'exo/buffer.cc', + 'exo/buffer.h', + 'exo/display.cc', + 'exo/display.h', + 'exo/shared_memory.cc', + 'exo/shared_memory.h', + 'exo/shell_surface.cc', + 'exo/shell_surface.h', + 'exo/surface.cc', + 'exo/surface.h', + 'exo/surface_delegate.h', + ], + }, + ], + 'conditions': [ + [ 'OS=="linux"', { + 'targets': [ + { + # GN version: //components/exo:wayland + 'target_name': 'exo_wayland', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../skia/skia.gyp:skia', + '../third_party/wayland/wayland.gyp:wayland_server', + 'exo', + ], + 'sources': [ + # Note: sources list duplicated in GN build. + 'exo/wayland/scoped_wl_types.cc', + 'exo/wayland/scoped_wl_types.h', + 'exo/wayland/server.cc', + 'exo/wayland/server.h', + ], + }, + ], + }], + ], +} diff --git a/components/exo/BUILD.gn b/components/exo/BUILD.gn new file mode 100644 index 0000000..64378b8 --- /dev/null +++ b/components/exo/BUILD.gn @@ -0,0 +1,88 @@ +# Copyright 2015 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. + +import("//testing/test.gni") + +source_set("exo") { + sources = [ + "buffer.cc", + "buffer.h", + "display.cc", + "display.h", + "shared_memory.cc", + "shared_memory.h", + "shell_surface.cc", + "shell_surface.h", + "surface.cc", + "surface.h", + "surface_delegate.h", + ] + + deps = [ + "//ash", + "//base", + "//cc", + "//gpu", + "//skia", + "//ui/aura", + "//ui/compositor", + "//ui/gfx", + "//ui/gfx/geometry", + "//ui/gl", + "//ui/views", + ] +} + +source_set("test_support") { + testonly = true + + sources = [ + "test/exo_test_base.cc", + "test/exo_test_base.h", + "test/exo_test_helper.cc", + "test/exo_test_helper.h", + ] + + deps = [ + "//ash:test_support", + "//base", + "//skia", + "//testing/gtest", + ] +} + +source_set("unit_tests") { + testonly = true + + sources = [ + "buffer_unittest.cc", + "display_unittest.cc", + "shared_memory_unittest.cc", + "shell_surface_unittest.cc", + "surface_unittest.cc", + ] + + deps = [ + "//components/user_manager", + "//skia", + "//testing/gtest", + "//ui/keyboard", + "//ui/message_center", + ":exo", + ":test_support", + ] +} + +test("exo_unittests") { + sources = [ + "test/run_all_unittests.cc", + ] + deps = [ + "//ash:test_support", + ":unit_tests", + ] + if (is_linux) { + deps += [ "//components/exo/wayland:unit_tests" ] + } +} diff --git a/components/exo/DEPS b/components/exo/DEPS new file mode 100644 index 0000000..af9ba7f --- /dev/null +++ b/components/exo/DEPS @@ -0,0 +1,8 @@ +include_rules = [ + "+ash", + "+cc", + "+gpu", + "+third_party/khronos", + "+third_party/skia", + "+ui", +] diff --git a/components/exo/OWNERS b/components/exo/OWNERS new file mode 100644 index 0000000..157836a --- /dev/null +++ b/components/exo/OWNERS @@ -0,0 +1 @@ +reveman@chromium.org diff --git a/components/exo/PRESUBMIT.py b/components/exo/PRESUBMIT.py new file mode 100644 index 0000000..1f71871 --- /dev/null +++ b/components/exo/PRESUBMIT.py @@ -0,0 +1,12 @@ +# Copyright 2015 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. + +"""Top-level presubmit script for Exosphere. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details about the presubmit API built into depot_tools. +""" + +def CheckChangeOnUpload(input_api, output_api): + return input_api.canned_checks.CheckPatchFormatted(input_api, output_api) diff --git a/components/exo/README b/components/exo/README new file mode 100644 index 0000000..81ae61f --- /dev/null +++ b/components/exo/README @@ -0,0 +1,2 @@ +This directory contains an implementation of a display server on top of +the Aura Shell. diff --git a/components/exo/buffer.cc b/components/exo/buffer.cc new file mode 100644 index 0000000..2f229ad --- /dev/null +++ b/components/exo/buffer.cc @@ -0,0 +1,187 @@ +// Copyright 2015 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 "components/exo/buffer.h" + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <GLES2/gl2extchromium.h> + +#include <algorithm> + +#include "base/logging.h" +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_event_argument.h" +#include "cc/output/context_provider.h" +#include "cc/resources/single_release_callback.h" +#include "cc/resources/texture_mailbox.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "ui/aura/env.h" +#include "ui/compositor/compositor.h" +#include "ui/gfx/gpu_memory_buffer.h" + +namespace exo { +namespace { + +GLenum GLInternalFormat(gfx::BufferFormat format) { + const GLenum kGLInternalFormats[] = { + GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD, // ATC + GL_COMPRESSED_RGB_S3TC_DXT1_EXT, // ATCIA + GL_COMPRESSED_RGB_S3TC_DXT1_EXT, // DXT1 + GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, // DXT5 + GL_ETC1_RGB8_OES, // ETC1 + GL_R8_EXT, // R_8 + GL_RGBA, // RGBA_4444 + GL_RGB, // RGBX_8888 + GL_RGBA, // RGBA_8888 + GL_RGB, // BGRX_8888 + GL_BGRA_EXT, // BGRA_8888 + GL_RGB_YUV_420_CHROMIUM, // YUV_420 + GL_INVALID_ENUM, // YUV_420_BIPLANAR + GL_RGB_YCBCR_422_CHROMIUM, // UYVY_422 + }; + static_assert(arraysize(kGLInternalFormats) == + (static_cast<int>(gfx::BufferFormat::LAST) + 1), + "BufferFormat::LAST must be last value of kGLInternalFormats"); + + DCHECK(format <= gfx::BufferFormat::LAST); + return kGLInternalFormats[static_cast<int>(format)]; +} + +gpu::gles2::GLES2Interface* GetContextGL() { + ui::ContextFactory* context_factory = + aura::Env::GetInstance()->context_factory(); + return context_factory->SharedMainThreadContextProvider()->ContextGL(); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// Buffer, public: + +Buffer::Buffer(scoped_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer, + unsigned texture_target) + : gpu_memory_buffer_(gpu_memory_buffer.Pass()), + texture_target_(texture_target), + texture_id_(0), + image_id_(0) { + gpu::gles2::GLES2Interface* gles2 = GetContextGL(); + // Create an image for |gpu_memory_buffer_|. + gfx::Size size = gpu_memory_buffer_->GetSize(); + image_id_ = gles2->CreateImageCHROMIUM( + gpu_memory_buffer_->AsClientBuffer(), size.width(), size.height(), + GLInternalFormat(gpu_memory_buffer_->GetFormat())); + // Create a texture with |texture_target_|. + gles2->ActiveTexture(GL_TEXTURE0); + gles2->GenTextures(1, &texture_id_); + gles2->BindTexture(texture_target_, texture_id_); + gles2->TexParameteri(texture_target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gles2->TexParameteri(texture_target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gles2->TexParameteri(texture_target_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gles2->TexParameteri(texture_target_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // Generate a crypto-secure random mailbox name. + gles2->GenMailboxCHROMIUM(mailbox_.name); + gles2->ProduceTextureCHROMIUM(texture_target_, mailbox_.name); +} + +Buffer::~Buffer() { + gpu::gles2::GLES2Interface* gles2 = GetContextGL(); + if (texture_id_) + gles2->DeleteTextures(1, &texture_id_); + if (image_id_) + gles2->DestroyImageCHROMIUM(image_id_); +} + +scoped_ptr<cc::SingleReleaseCallback> Buffer::AcquireTextureMailbox( + cc::TextureMailbox* texture_mailbox) { + // Buffer can only be used by one client at a time. If texture id is 0, then a + // previous call to AcquireTextureMailbox() is using this buffer and it has + // not been released yet. + if (!texture_id_) { + DLOG(WARNING) << "Client tried to use a buffer that has not been released"; + return nullptr; + } + + // Take ownerhsip of image and texture ids. + unsigned texture_id = 0; + unsigned image_id = 0; + std::swap(texture_id, texture_id_); + DCHECK_NE(image_id_, 0u); + std::swap(image_id, image_id_); + + // Bind texture to |texture_target_|. + gpu::gles2::GLES2Interface* gles2 = GetContextGL(); + gles2->ActiveTexture(GL_TEXTURE0); + gles2->BindTexture(texture_target_, texture_id); + + // Bind the image to texture. + gles2->BindTexImage2DCHROMIUM(texture_target_, image_id); + + // Create a sync token to ensure that the BindTexImage2DCHROMIUM call is + // processed before issuing any commands that will read from texture. + uint64 fence_sync = gles2->InsertFenceSyncCHROMIUM(); + gles2->ShallowFlushCHROMIUM(); + gpu::SyncToken sync_token; + gles2->GenUnverifiedSyncTokenCHROMIUM(fence_sync, sync_token.GetData()); + + bool is_overlay_candidate = false; + *texture_mailbox = + cc::TextureMailbox(mailbox_, sync_token, texture_target_, + gpu_memory_buffer_->GetSize(), is_overlay_candidate); + return cc::SingleReleaseCallback::Create( + base::Bind(&Buffer::Release, AsWeakPtr(), texture_target_, + texture_id, image_id)) + .Pass(); +} + +gfx::Size Buffer::GetSize() const { + return gpu_memory_buffer_->GetSize(); +} + +scoped_refptr<base::trace_event::TracedValue> Buffer::AsTracedValue() const { + scoped_refptr<base::trace_event::TracedValue> value = + new base::trace_event::TracedValue; + value->SetInteger("width", GetSize().width()); + value->SetInteger("height", GetSize().height()); + value->SetInteger("format", + static_cast<int>(gpu_memory_buffer_->GetFormat())); + return value; +} + +//////////////////////////////////////////////////////////////////////////////// +// Buffer, private: + +// static +void Buffer::Release(base::WeakPtr<Buffer> buffer, + unsigned texture_target, + unsigned texture_id, + unsigned image_id, + const gpu::SyncToken& sync_token, + bool is_lost) { + TRACE_EVENT1("exo", "Buffer::Release", "is_lost", is_lost); + + gpu::gles2::GLES2Interface* gles2 = GetContextGL(); + if (sync_token.HasData()) + gles2->WaitSyncTokenCHROMIUM(sync_token.GetConstData()); + gles2->ActiveTexture(GL_TEXTURE0); + gles2->BindTexture(texture_target, texture_id); + gles2->ReleaseTexImage2DCHROMIUM(texture_target, image_id); + + // Delete resources and return if buffer is gone. + if (!buffer) { + gles2->DeleteTextures(1, &texture_id); + gles2->DestroyImageCHROMIUM(image_id); + return; + } + + DCHECK_EQ(buffer->texture_id_, 0u); + buffer->texture_id_ = texture_id; + DCHECK_EQ(buffer->image_id_, 0u); + buffer->image_id_ = image_id; + + if (!buffer->release_callback_.is_null()) + buffer->release_callback_.Run(); +} + +} // namespace exo diff --git a/components/exo/buffer.h b/components/exo/buffer.h new file mode 100644 index 0000000..192513b --- /dev/null +++ b/components/exo/buffer.h @@ -0,0 +1,82 @@ +// Copyright 2015 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 COMPONENTS_EXO_BUFFER_H_ +#define COMPONENTS_EXO_BUFFER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "gpu/command_buffer/common/mailbox.h" +#include "gpu/command_buffer/common/sync_token.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/geometry/size.h" + +namespace base { +namespace trace_event { +class TracedValue; +} +} + +namespace cc { +class SingleReleaseCallback; +class TextureMailbox; +} + +namespace gfx { +class GpuMemoryBuffer; +} + +namespace exo { + +// This class provides the content for a Surface. The mechanism by which a +// client provides and updates the contents is the responsibility of the client +// and not defined as part of this class. +class Buffer : public base::SupportsWeakPtr<Buffer> { + public: + Buffer(scoped_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer, + unsigned texture_target); + ~Buffer(); + + // Set the callback to run when the buffer is no longer used by the + // compositor. The client is free to re-use or destroy this buffer and + // its backing storage after this has been called. + void set_release_callback(const base::Closure& release_callback) { + release_callback_ = release_callback; + } + + // This function can be used to acquire a texture mailbox that is bound to + // the buffer. Returns a release callback on success. The release callback + // must be called before a new texture mailbox can be acquired. + scoped_ptr<cc::SingleReleaseCallback> AcquireTextureMailbox( + cc::TextureMailbox* mailbox); + + // Returns the size of the buffer. + gfx::Size GetSize() const; + + // Returns a trace value representing the state of the buffer. + scoped_refptr<base::trace_event::TracedValue> AsTracedValue() const; + + private: + static void Release(base::WeakPtr<Buffer> buffer, + unsigned texture_target, + unsigned texture_id, + unsigned image_id, + const gpu::SyncToken& sync_token, + bool is_lost); + + scoped_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer_; + const unsigned texture_target_; + unsigned texture_id_; + unsigned image_id_; + gpu::Mailbox mailbox_; + base::Closure release_callback_; + + DISALLOW_COPY_AND_ASSIGN(Buffer); +}; + +} // namespace exo + +#endif // COMPONENTS_EXO_BUFFER_H_ diff --git a/components/exo/buffer_unittest.cc b/components/exo/buffer_unittest.cc new file mode 100644 index 0000000..4da72f4 --- /dev/null +++ b/components/exo/buffer_unittest.cc @@ -0,0 +1,52 @@ +// Copyright 2015 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 "base/bind.h" +#include "cc/resources/single_release_callback.h" +#include "components/exo/buffer.h" +#include "components/exo/surface.h" +#include "components/exo/test/exo_test_base.h" +#include "components/exo/test/exo_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "ui/gfx/gpu_memory_buffer.h" + +namespace exo { +namespace { + +using BufferTest = test::ExoTestBase; + +void Release(int* release_call_count) { + (*release_call_count)++; +} + +TEST_F(BufferTest, ReleaseCallback) { + gfx::Size buffer_size(256, 256); + scoped_ptr<Buffer> buffer( + new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size).Pass(), + GL_TEXTURE_2D)); + + // Set the release callback. + int release_call_count = 0; + buffer->set_release_callback( + base::Bind(&Release, base::Unretained(&release_call_count))); + + // Acquire a texture mailbox for the contents of the buffer. + cc::TextureMailbox texture_mailbox; + scoped_ptr<cc::SingleReleaseCallback> buffer_release_callback = + buffer->AcquireTextureMailbox(&texture_mailbox); + ASSERT_TRUE(buffer_release_callback); + + // Trying to acquire an already in-use buffer should fail. + EXPECT_FALSE(buffer->AcquireTextureMailbox(&texture_mailbox)); + + // Release buffer. + buffer_release_callback->Run(gpu::SyncToken(), false); + + // Release() should have been called exactly once. + ASSERT_EQ(release_call_count, 1); +} + +} // namespace +} // namespace exo diff --git a/components/exo/display.cc b/components/exo/display.cc new file mode 100644 index 0000000..bb6a017 --- /dev/null +++ b/components/exo/display.cc @@ -0,0 +1,46 @@ +// Copyright 2015 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 "components/exo/display.h" + +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_event_argument.h" +#include "components/exo/shared_memory.h" +#include "components/exo/shell_surface.h" +#include "components/exo/surface.h" + +namespace exo { + +//////////////////////////////////////////////////////////////////////////////// +// Display, public: + +Display::Display() {} + +Display::~Display() {} + +scoped_ptr<Surface> Display::CreateSurface() { + TRACE_EVENT0("exo", "Display::CreateSurface"); + + return make_scoped_ptr(new Surface); +} + +scoped_ptr<SharedMemory> Display::CreateSharedMemory( + const base::SharedMemoryHandle& handle, + size_t size) { + TRACE_EVENT1("exo", "Display::CreateSharedMemory", "size", size); + + if (!base::SharedMemory::IsHandleValid(handle)) + return nullptr; + + return make_scoped_ptr(new SharedMemory(handle)); +} + +scoped_ptr<ShellSurface> Display::CreateShellSurface(Surface* surface) { + TRACE_EVENT1("exo", "Display::CreateShellSurface", "surface", + surface->AsTracedValue()); + + return make_scoped_ptr(new ShellSurface(surface)); +} + +} // namespace exo diff --git a/components/exo/display.h b/components/exo/display.h new file mode 100644 index 0000000..472f4c9 --- /dev/null +++ b/components/exo/display.h @@ -0,0 +1,43 @@ +// Copyright 2015 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 COMPONENTS_EXO_DISPLAY_H_ +#define COMPONENTS_EXO_DISPLAY_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/shared_memory_handle.h" + +namespace exo { +class SharedMemory; +class ShellSurface; +class Surface; + +// The core display class. This class provides functions for creating surfaces +// and is in charge of combining the contents of multiple surfaces into one +// displayable output. +class Display { + public: + Display(); + ~Display(); + + // Creates a new surface. + scoped_ptr<Surface> CreateSurface(); + + // Creates a shared memory segment from |handle| of |size| with the + // given |id|. This function takes ownership of |handle|. + scoped_ptr<SharedMemory> CreateSharedMemory( + const base::SharedMemoryHandle& handle, + size_t size); + + // Creates a shell surface for an existing surface. + scoped_ptr<ShellSurface> CreateShellSurface(Surface* surface); + + private: + DISALLOW_COPY_AND_ASSIGN(Display); +}; + +} // namespace exo + +#endif // COMPONENTS_EXO_DISPLAY_H_ diff --git a/components/exo/display_unittest.cc b/components/exo/display_unittest.cc new file mode 100644 index 0000000..8274529 --- /dev/null +++ b/components/exo/display_unittest.cc @@ -0,0 +1,67 @@ +// Copyright 2015 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 "components/exo/buffer.h" +#include "components/exo/display.h" +#include "components/exo/shared_memory.h" +#include "components/exo/shell_surface.h" +#include "components/exo/surface.h" +#include "components/exo/test/exo_test_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace exo { +namespace { + +using DisplayTest = test::ExoTestBase; + +TEST_F(DisplayTest, CreateSurface) { + scoped_ptr<Display> display(new Display); + + // Creating a surface should succeed. + scoped_ptr<Surface> surface = display->CreateSurface(); + EXPECT_TRUE(surface); +} + +TEST_F(DisplayTest, CreateSharedMemory) { + scoped_ptr<Display> display(new Display); + + int shm_size = 8192; + scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory); + bool rv = shared_memory->CreateAnonymous(shm_size); + ASSERT_TRUE(rv); + + base::SharedMemoryHandle handle = + base::SharedMemory::DuplicateHandle(shared_memory->handle()); + ASSERT_TRUE(base::SharedMemory::IsHandleValid(handle)); + + // Creating a shared memory instance from a valid handle should succeed. + scoped_ptr<SharedMemory> shm1 = display->CreateSharedMemory(handle, shm_size); + EXPECT_TRUE(shm1); + + // Creating a shared memory instance from a invalid handle should fail. + scoped_ptr<SharedMemory> shm2 = + display->CreateSharedMemory(base::SharedMemoryHandle(), shm_size); + EXPECT_FALSE(shm2); +} + +TEST_F(DisplayTest, CreateShellSurface) { + scoped_ptr<Display> display(new Display); + + // Create two surfaces. + scoped_ptr<Surface> surface1 = display->CreateSurface(); + EXPECT_TRUE(surface1); + scoped_ptr<Surface> surface2 = display->CreateSurface(); + EXPECT_TRUE(surface2); + + // Create a shell surface for surface1. + scoped_ptr<ShellSurface> shell_surface1 = + display->CreateShellSurface(surface1.get()); + + // Create a shell surface for surface2. + scoped_ptr<ShellSurface> shell_surface2 = + display->CreateShellSurface(surface2.get()); +} + +} // namespace +} // namespace exo diff --git a/components/exo/shared_memory.cc b/components/exo/shared_memory.cc new file mode 100644 index 0000000..912cb8c --- /dev/null +++ b/components/exo/shared_memory.cc @@ -0,0 +1,79 @@ +// Copyright 2015 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 "components/exo/shared_memory.h" + +#include "base/logging.h" +#include "base/trace_event/trace_event.h" +#include "components/exo/buffer.h" +#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "ui/aura/env.h" +#include "ui/compositor/compositor.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gpu_memory_buffer.h" + +namespace exo { +namespace { + +bool IsSupportedFormat(gfx::BufferFormat format) { + return format == gfx::BufferFormat::RGBX_8888 || + format == gfx::BufferFormat::RGBA_8888 || + format == gfx::BufferFormat::BGRX_8888 || + format == gfx::BufferFormat::BGRA_8888; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// SharedMemory, public: + +SharedMemory::SharedMemory(const base::SharedMemoryHandle& handle) + : shared_memory_(handle, true /* read-only */) {} + +SharedMemory::~SharedMemory() {} + +scoped_ptr<Buffer> SharedMemory::CreateBuffer(const gfx::Size& size, + gfx::BufferFormat format, + unsigned offset, + int stride) { + TRACE_EVENT2("exo", "SharedMemory::CreateBuffer", "size", size.ToString(), + "format", static_cast<int>(format)); + + if (!IsSupportedFormat(format)) { + DLOG(WARNING) << "Failed to create shm buffer. Unsupported format 0x" + << static_cast<int>(format); + return nullptr; + } + + if (!base::IsValueInRangeForNumericType<size_t>(stride) || + gfx::RowSizeForBufferFormat(size.width(), format, 0) > + static_cast<size_t>(stride) || + static_cast<size_t>(stride) & 3) { + DLOG(WARNING) << "Failed to create shm buffer. Unsupported stride " + << stride; + return nullptr; + } + + gfx::GpuMemoryBufferHandle handle; + handle.type = gfx::SHARED_MEMORY_BUFFER; + handle.handle = base::SharedMemory::DuplicateHandle(shared_memory_.handle()); + handle.offset = offset; + handle.stride = stride; + + scoped_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer = + aura::Env::GetInstance() + ->context_factory() + ->GetGpuMemoryBufferManager() + ->CreateGpuMemoryBufferFromHandle(handle, size, format); + if (!gpu_memory_buffer) { + LOG(ERROR) << "Failed to create GpuMemoryBuffer from handle"; + return nullptr; + } + + return make_scoped_ptr(new Buffer(gpu_memory_buffer.Pass(), GL_TEXTURE_2D)); +} + +} // namespace exo diff --git a/components/exo/shared_memory.h b/components/exo/shared_memory.h new file mode 100644 index 0000000..53ba8ce --- /dev/null +++ b/components/exo/shared_memory.h @@ -0,0 +1,42 @@ +// Copyright 2015 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 COMPONENTS_EXO_SHARED_MEMORY_H_ +#define COMPONENTS_EXO_SHARED_MEMORY_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/shared_memory.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/geometry/size.h" + +namespace exo { +class Buffer; + +// Shared memory abstraction. Provides a wrapper around base::SharedMemory +// with functionality to create buffers from the memory to use for display. +class SharedMemory { + public: + explicit SharedMemory(const base::SharedMemoryHandle& handle); + ~SharedMemory(); + + // Creates a buffer from the shared memory. The buffer is created offset bytes + // into the memory and has width and height as specified. The stride + // arguments specifies the number of bytes from beginning of one row to the + // beginning of the next. The format is the pixel format of the buffer and + // must be one of RGBX_8888, RGBA_8888, BGRX_8888, BGRA_8888. + scoped_ptr<Buffer> CreateBuffer(const gfx::Size& size, + gfx::BufferFormat format, + unsigned offset, + int stride); + + private: + base::SharedMemory shared_memory_; + + DISALLOW_COPY_AND_ASSIGN(SharedMemory); +}; + +} // namespace exo + +#endif // COMPONENTS_EXO_SHARED_MEMORY_H_ diff --git a/components/exo/shared_memory_unittest.cc b/components/exo/shared_memory_unittest.cc new file mode 100644 index 0000000..3579630 --- /dev/null +++ b/components/exo/shared_memory_unittest.cc @@ -0,0 +1,59 @@ +// Copyright 2015 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 "components/exo/buffer.h" +#include "components/exo/shared_memory.h" +#include "components/exo/test/exo_test_base.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/buffer_format_util.h" + +namespace exo { +namespace { + +using SharedMemoryTest = test::ExoTestBase; + +scoped_ptr<SharedMemory> CreateSharedMemory(size_t size) { + scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory); + bool rv = shared_memory->CreateAnonymous(size); + DCHECK(rv); + base::SharedMemoryHandle handle = + base::SharedMemory::DuplicateHandle(shared_memory->handle()); + DCHECK(base::SharedMemory::IsHandleValid(handle)); + return make_scoped_ptr(new SharedMemory(handle)); +} + +TEST_F(SharedMemoryTest, CreateBuffer) { + const gfx::Size buffer_size(256, 256); + const gfx::BufferFormat format = gfx::BufferFormat::RGBA_8888; + + scoped_ptr<SharedMemory> shared_memory = + CreateSharedMemory(gfx::BufferSizeForBufferFormat(buffer_size, format)); + ASSERT_TRUE(shared_memory); + + // Creating a full size buffer should succeed. + scoped_ptr<Buffer> buffer = shared_memory->CreateBuffer( + buffer_size, format, 0, + gfx::RowSizeForBufferFormat(buffer_size.width(), format, 0)); + EXPECT_TRUE(buffer); + + // Creating a buffer for the top-left rectangle should succeed. + const gfx::Size top_left_buffer_size(128, 128); + scoped_ptr<Buffer> top_left_buffer = shared_memory->CreateBuffer( + top_left_buffer_size, format, 0, + gfx::RowSizeForBufferFormat(buffer_size.width(), format, 0)); + EXPECT_TRUE(top_left_buffer); + + // Creating a buffer for the bottom-right rectangle should succeed. + const gfx::Size bottom_right_buffer_size(64, 64); + scoped_ptr<Buffer> bottom_right_buffer = shared_memory->CreateBuffer( + bottom_right_buffer_size, format, + (buffer_size.height() - bottom_right_buffer_size.height()) * + gfx::RowSizeForBufferFormat(buffer_size.width(), format, 0) + + (buffer_size.width() - bottom_right_buffer_size.width()) * 4, + gfx::RowSizeForBufferFormat(buffer_size.width(), format, 0)); + EXPECT_TRUE(bottom_right_buffer); +} + +} // namespace +} // namespace exo diff --git a/components/exo/shell_surface.cc b/components/exo/shell_surface.cc new file mode 100644 index 0000000..0e7e90a --- /dev/null +++ b/components/exo/shell_surface.cc @@ -0,0 +1,164 @@ +// Copyright 2015 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 "components/exo/shell_surface.h" + +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_event_argument.h" +#include "components/exo/surface.h" +#include "ui/aura/window.h" +#include "ui/base/hit_test.h" +#include "ui/views/widget/widget.h" + +namespace exo { +namespace { + +class CustomFrameView : public views::NonClientFrameView { + public: + explicit CustomFrameView(views::Widget* widget) : widget_(widget) {} + ~CustomFrameView() override {} + + // Overridden from views::NonClientFrameView: + gfx::Rect GetBoundsForClientView() const override { return bounds(); } + gfx::Rect GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const override { + return client_bounds; + } + int NonClientHitTest(const gfx::Point& point) override { + return widget_->client_view()->NonClientHitTest(point); + } + void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask) override {} + void ResetWindowControls() override {} + void UpdateWindowIcon() override {} + void UpdateWindowTitle() override {} + void SizeConstraintsChanged() override {} + + private: + views::Widget* const widget_; + + DISALLOW_COPY_AND_ASSIGN(CustomFrameView); +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// ShellSurface, public: + +ShellSurface::ShellSurface(Surface* surface) + : surface_(surface), show_state_(ui::SHOW_STATE_END) { + surface_->SetSurfaceDelegate(this); +} + +ShellSurface::~ShellSurface() { + if (surface_) + surface_->SetSurfaceDelegate(nullptr); + if (widget_) + widget_->CloseNow(); +} + +void ShellSurface::Show() { + TRACE_EVENT0("exo", "ShellSurface::Show"); + + if (!widget_ && show_state_ == ui::SHOW_STATE_END) + show_state_ = ui::SHOW_STATE_DEFAULT; +} + +void ShellSurface::SetToplevel() { + TRACE_EVENT0("exo", "ShellSurface::SetToplevel"); + + if (!widget_) + show_state_ = ui::SHOW_STATE_NORMAL; +} + +void ShellSurface::SetFullscreen(bool fullscreen) { + TRACE_EVENT1("exo", "ShellSurface::SetFullscreen", "fullscreen", fullscreen); + + if (widget_) { + widget_->SetFullscreen(fullscreen); + return; + } + + show_state_ = fullscreen ? ui::SHOW_STATE_FULLSCREEN : ui::SHOW_STATE_DEFAULT; +} + +void ShellSurface::SetTitle(const base::string16& title) { + TRACE_EVENT1("exo", "ShellSurface::SetTitle", "title", + base::UTF16ToUTF8(title)); + + title_ = title; + if (widget_) + widget_->UpdateWindowTitle(); +} + +scoped_refptr<base::trace_event::TracedValue> ShellSurface::AsTracedValue() + const { + scoped_refptr<base::trace_event::TracedValue> value = + new base::trace_event::TracedValue; + value->SetString("title", base::UTF16ToUTF8(title_)); + return value; +} + +//////////////////////////////////////////////////////////////////////////////// +// SurfaceDelegate overrides: + +void ShellSurface::OnSurfaceDestroying() { + surface_ = nullptr; +} + +void ShellSurface::OnSurfaceCommit() { + if (widget_ || show_state_ == ui::SHOW_STATE_END) + return; + + views::Widget::InitParams params; + params.type = views::Widget::InitParams::TYPE_WINDOW; + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.delegate = this; + params.shadow_type = show_state_ == ui::SHOW_STATE_NORMAL + ? views::Widget::InitParams::SHADOW_TYPE_DROP + : views::Widget::InitParams::SHADOW_TYPE_NONE; + params.opacity = show_state_ == ui::SHOW_STATE_NORMAL + ? views::Widget::InitParams::OPAQUE_WINDOW + : views::Widget::InitParams::TRANSLUCENT_WINDOW; + params.show_state = show_state_; + params.parent = ash::Shell::GetContainer( + ash::Shell::GetPrimaryRootWindow(), ash::kShellWindowId_DefaultContainer); + widget_.reset(new views::Widget); + widget_->Init(params); + widget_->GetNativeWindow()->set_owned_by_parent(false); + widget_->GetNativeView()->SetName("ShellSurface"); + widget_->Show(); +} + +//////////////////////////////////////////////////////////////////////////////// +// views::WidgetDelegate overrides: + +base::string16 ShellSurface::GetWindowTitle() const { + return title_; +} + +views::Widget* ShellSurface::GetWidget() { + return widget_.get(); +} + +const views::Widget* ShellSurface::GetWidget() const { + return widget_.get(); +} + +views::View* ShellSurface::GetContentsView() { + return surface_; +} + +views::NonClientFrameView* ShellSurface::CreateNonClientFrameView( + views::Widget* widget) { + // Default show state is borderless and requires a custom frame view as the + // default one does not support this. + return show_state_ != ui::SHOW_STATE_NORMAL ? new CustomFrameView(widget) + : nullptr; +} + +} // namespace exo diff --git a/components/exo/shell_surface.h b/components/exo/shell_surface.h new file mode 100644 index 0000000..e997d01 --- /dev/null +++ b/components/exo/shell_surface.h @@ -0,0 +1,71 @@ +// Copyright 2015 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 COMPONENTS_EXO_SHELL_SURFACE_H_ +#define COMPONENTS_EXO_SHELL_SURFACE_H_ + +#include <string> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "components/exo/surface_delegate.h" +#include "ui/views/widget/widget_delegate.h" + +namespace base { +namespace trace_event { +class TracedValue; +} +} + +namespace exo { +class Surface; + +// This class provides functions for treating a surfaces like toplevel, +// fullscreen or popup widgets, move, resize or maximize them, associate +// metadata like title and class, etc. +class ShellSurface : public SurfaceDelegate, public views::WidgetDelegate { + public: + explicit ShellSurface(Surface* surface); + ~ShellSurface() override; + + // Show surface at the time of the next commit. + void Show(); + + // Show surface as a toplevel window with decorations. + void SetToplevel(); + + // Make the surface fullscreen. + void SetFullscreen(bool fullscreen); + + // Set title for surface. + void SetTitle(const base::string16& title); + + // Returns a trace value representing the state of the surface. + scoped_refptr<base::trace_event::TracedValue> AsTracedValue() const; + + // Overridden from SurfaceDelegate: + void OnSurfaceDestroying() override; + void OnSurfaceCommit() override; + + // Overridden from views::WidgetDelegate: + base::string16 GetWindowTitle() const override; + views::Widget* GetWidget() override; + const views::Widget* GetWidget() const override; + views::View* GetContentsView() override; + views::NonClientFrameView* CreateNonClientFrameView( + views::Widget* widget) override; + + private: + scoped_ptr<views::Widget> widget_; + Surface* surface_; + ui::WindowShowState show_state_; + base::string16 title_; + + DISALLOW_COPY_AND_ASSIGN(ShellSurface); +}; + +} // namespace exo + +#endif // COMPONENTS_EXO_SHELL_SURFACE_H_ diff --git a/components/exo/shell_surface_unittest.cc b/components/exo/shell_surface_unittest.cc new file mode 100644 index 0000000..6ade284 --- /dev/null +++ b/components/exo/shell_surface_unittest.cc @@ -0,0 +1,54 @@ +// Copyright 2015 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 "base/strings/utf_string_conversions.h" +#include "components/exo/shell_surface.h" +#include "components/exo/surface.h" +#include "components/exo/test/exo_test_base.h" +#include "components/exo/test/exo_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace exo { +namespace { + +using ShellSurfaceTest = test::ExoTestBase; + +TEST_F(ShellSurfaceTest, Show) { + scoped_ptr<Surface> surface(new Surface); + scoped_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); + + shell_surface->Show(); + surface->Commit(); +} + +TEST_F(ShellSurfaceTest, SetToplevel) { + scoped_ptr<Surface> surface(new Surface); + scoped_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); + + shell_surface->SetToplevel(); + surface->Commit(); +} + +TEST_F(ShellSurfaceTest, SetFullscreen) { + scoped_ptr<Surface> surface(new Surface); + scoped_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); + + shell_surface->SetFullscreen(true); + surface->Commit(); + + // Fullscreen mode can change after the initial Commit(). + shell_surface->SetFullscreen(false); + surface->Commit(); +} + +TEST_F(ShellSurfaceTest, SetTitle) { + scoped_ptr<Surface> surface(new Surface); + scoped_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); + + shell_surface->SetTitle(base::string16(base::ASCIIToUTF16("test"))); + surface->Commit(); +} + +} // namespace +} // namespace exo diff --git a/components/exo/surface.cc b/components/exo/surface.cc new file mode 100644 index 0000000..6e21edc --- /dev/null +++ b/components/exo/surface.cc @@ -0,0 +1,160 @@ +// Copyright 2015 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 "components/exo/surface.h" + +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_event_argument.h" +#include "cc/resources/single_release_callback.h" +#include "components/exo/buffer.h" +#include "components/exo/surface_delegate.h" +#include "ui/compositor/layer.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/gpu_memory_buffer.h" + +namespace exo { + +//////////////////////////////////////////////////////////////////////////////// +// Surface, public: + +Surface::Surface() : compositor_(nullptr), delegate_(nullptr) { + SetLayer(new ui::Layer(ui::LAYER_SOLID_COLOR)); + set_owned_by_client(); +} + +Surface::~Surface() { + if (delegate_) + delegate_->OnSurfaceDestroying(); + + layer()->SetShowSolidColorContent(); + + if (compositor_) + compositor_->RemoveObserver(this); + + // Call pending frame callbacks with a null frame time to indicate that they + // have been cancelled. + frame_callbacks_.splice(frame_callbacks_.end(), pending_frame_callbacks_); + active_frame_callbacks_.splice(active_frame_callbacks_.end(), + frame_callbacks_); + for (const auto& frame_callback : active_frame_callbacks_) + frame_callback.Run(base::TimeTicks()); +} + +void Surface::Attach(Buffer* buffer) { + TRACE_EVENT1("exo", "Surface::Attach", "buffer", buffer->AsTracedValue()); + + pending_buffer_ = buffer ? buffer->AsWeakPtr() : base::WeakPtr<Buffer>(); + PreferredSizeChanged(); +} + +void Surface::Damage(const gfx::Rect& damage) { + TRACE_EVENT1("exo", "Surface::Damage", "damage", damage.ToString()); + + pending_damage_.Union(damage); +} + +void Surface::RequestFrameCallback(const FrameCallback& callback) { + TRACE_EVENT0("exo", "Surface::RequestFrameCallback"); + + pending_frame_callbacks_.push_back(callback); +} + +void Surface::SetOpaqueRegion(const SkRegion& region) { + TRACE_EVENT1("exo", "Surface::SetOpaqueRegion", "region", + gfx::SkIRectToRect(region.getBounds()).ToString()); + + pending_opaque_region_ = region; +} + +void Surface::Commit() { + TRACE_EVENT0("exo", "Surface::Commit"); + + if (delegate_) + delegate_->OnSurfaceCommit(); + + cc::TextureMailbox texture_mailbox; + scoped_ptr<cc::SingleReleaseCallback> texture_mailbox_release_callback; + if (pending_buffer_) { + texture_mailbox_release_callback = + pending_buffer_->AcquireTextureMailbox(&texture_mailbox); + pending_buffer_.reset(); + } else { + // Show solid color content if there is no pending buffer. + layer()->SetShowSolidColorContent(); + } + + if (texture_mailbox_release_callback) { + // Update layer with the new contents. + layer()->SetTextureMailbox(texture_mailbox, + texture_mailbox_release_callback.Pass(), + texture_mailbox.size_in_pixels()); + layer()->SetTextureFlipped(false); + layer()->SetBounds(gfx::Rect(layer()->bounds().origin(), + texture_mailbox.size_in_pixels())); + layer()->SetFillsBoundsOpaquely(pending_opaque_region_.contains( + gfx::RectToSkIRect(gfx::Rect(texture_mailbox.size_in_pixels())))); + } + + // Schedule redraw of the damage region. + layer()->SchedulePaint(pending_damage_); + pending_damage_ = gfx::Rect(); + + ui::Compositor* compositor = layer()->GetCompositor(); + if (compositor && !pending_frame_callbacks_.empty()) { + // Start observing the compositor for frame callbacks. + if (!compositor_) { + compositor->AddObserver(this); + compositor_ = compositor; + } + + // Move pending frame callbacks to the end of |frame_callbacks_|. + frame_callbacks_.splice(frame_callbacks_.end(), pending_frame_callbacks_); + } +} + +void Surface::SetSurfaceDelegate(SurfaceDelegate* delegate) { + DCHECK(!delegate_ || !delegate); + delegate_ = delegate; +} + +scoped_refptr<base::trace_event::TracedValue> Surface::AsTracedValue() const { + scoped_refptr<base::trace_event::TracedValue> value = + new base::trace_event::TracedValue; + value->SetString("name", layer()->name()); + return value; +} + +//////////////////////////////////////////////////////////////////////////////// +// views::Views overrides: + +gfx::Size Surface::GetPreferredSize() const { + return pending_buffer_ ? pending_buffer_->GetSize() : layer()->size(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ui::CompositorObserver overrides: + +void Surface::OnCompositingDidCommit(ui::Compositor* compositor) { + // Move frame callbacks to the end of |active_frame_callbacks_|. + active_frame_callbacks_.splice(active_frame_callbacks_.end(), + frame_callbacks_); +} + +void Surface::OnCompositingStarted(ui::Compositor* compositor, + base::TimeTicks start_time) { + // Run all frame callbacks associated with the compositor's active tree. + while (!active_frame_callbacks_.empty()) { + active_frame_callbacks_.front().Run(start_time); + active_frame_callbacks_.pop_front(); + } +} + +void Surface::OnCompositingShuttingDown(ui::Compositor* compositor) { + compositor->RemoveObserver(this); + compositor_ = nullptr; +} + +} // namespace exo diff --git a/components/exo/surface.h b/components/exo/surface.h new file mode 100644 index 0000000..4db760e --- /dev/null +++ b/components/exo/surface.h @@ -0,0 +1,112 @@ +// Copyright 2015 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 COMPONENTS_EXO_SURFACE_H_ +#define COMPONENTS_EXO_SURFACE_H_ + +#include <list> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "ui/compositor/compositor_observer.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/views/view.h" + +namespace base { +namespace trace_event { +class TracedValue; +} +} + +namespace exo { +class Buffer; +class SurfaceDelegate; + +// This class represents a rectangular area that is displayed on the screen. +// It has a location, size and pixel contents. +class Surface : public views::View, public ui::CompositorObserver { + public: + Surface(); + ~Surface() override; + + // Set a buffer as the content of this surface. A buffer can only be attached + // to one surface at a time. + void Attach(Buffer* buffer); + + // Describe the regions where the pending buffer is different from the + // current surface contents, and where the surface therefore needs to be + // repainted. + void Damage(const gfx::Rect& rect); + + // Request notification when the next frame is displayed. Useful for + // throttling redrawing operations, and driving animations. + using FrameCallback = base::Callback<void(base::TimeTicks frame_time)>; + void RequestFrameCallback(const FrameCallback& callback); + + // This sets the region of the surface that contains opaque content. + void SetOpaqueRegion(const SkRegion& region); + + // Surface state (damage regions, attached buffers, etc.) is double-buffered. + // A Commit() call atomically applies all pending state, replacing the + // current state. + void Commit(); + + // Set the surface delegate. + void SetSurfaceDelegate(SurfaceDelegate* delegate); + + // Returns a trace value representing the state of the surface. + scoped_refptr<base::trace_event::TracedValue> AsTracedValue() const; + + bool HasPendingDamageForTesting() const { return !pending_damage_.IsEmpty(); } + + // Overridden from views::View: + gfx::Size GetPreferredSize() const override; + + // Overridden from ui::CompositorObserver: + void OnCompositingDidCommit(ui::Compositor* compositor) override; + void OnCompositingStarted(ui::Compositor* compositor, + base::TimeTicks start_time) override; + void OnCompositingEnded(ui::Compositor* compositor) override {} + void OnCompositingAborted(ui::Compositor* compositor) override {} + void OnCompositingLockStateChanged(ui::Compositor* compositor) override {} + void OnCompositingShuttingDown(ui::Compositor* compositor) override; + + private: + // The buffer that will become the content of surface when Commit() is called. + base::WeakPtr<Buffer> pending_buffer_; + + // The damage region to schedule paint for when Commit() is called. + // TODO(reveman): Use SkRegion here after adding a version of + // ui::Layer::SchedulePaint that takes a SkRegion. + gfx::Rect pending_damage_; + + // These lists contains the callbacks to notify the client when it is a good + // time to start producing a new frame. These callbacks move to + // |frame_callbacks_| when Commit() is called. Later they are moved to + // |active_frame_callbacks_| when the effect of the Commit() is reflected in + // the compositor's active layer tree. The callbacks fire once we're notified + // that the compositor started drawing that active layer tree. + std::list<FrameCallback> pending_frame_callbacks_; + std::list<FrameCallback> frame_callbacks_; + std::list<FrameCallback> active_frame_callbacks_; + + // The opaque region to take effect when Commit() is called. + SkRegion pending_opaque_region_; + + // The compsitor being observer or null if not observing a compositor. + ui::Compositor* compositor_; + + // This can be set to have some functions delegated. E.g. ShellSurface class + // can set this to handle Commit() and apply any double buffered state it + // maintains. + SurfaceDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(Surface); +}; + +} // namespace exo + +#endif // COMPONENTS_EXO_SURFACE_H_ diff --git a/components/exo/surface_delegate.h b/components/exo/surface_delegate.h new file mode 100644 index 0000000..bfda475 --- /dev/null +++ b/components/exo/surface_delegate.h @@ -0,0 +1,26 @@ +// Copyright 2015 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 COMPONENTS_EXO_SURFACE_DELEGATE_H_ +#define COMPONENTS_EXO_SURFACE_DELEGATE_H_ + +namespace exo { + +// Handles events on surfaces in context-specific ways. +class SurfaceDelegate { + public: + // Called when the surface is destroyed. The delegate can use this + // opportunity to delete itself if necessary. + virtual void OnSurfaceDestroying() = 0; + + // Called when surface was requested to commit all double-buffered state. + virtual void OnSurfaceCommit() = 0; + + protected: + virtual ~SurfaceDelegate() {} +}; + +} // namespace exo + +#endif // COMPONENTS_EXO_SURFACE_DELEGATE_H_ diff --git a/components/exo/surface_unittest.cc b/components/exo/surface_unittest.cc new file mode 100644 index 0000000..f867a8d --- /dev/null +++ b/components/exo/surface_unittest.cc @@ -0,0 +1,105 @@ +// Copyright 2015 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 "base/bind.h" +#include "components/exo/buffer.h" +#include "components/exo/surface.h" +#include "components/exo/test/exo_test_base.h" +#include "components/exo/test/exo_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "ui/gfx/gpu_memory_buffer.h" +#include "ui/views/test/widget_test.h" + +namespace exo { +namespace { + +using SurfaceTest = test::ExoTestBase; + +void ReleaseBuffer(int* release_buffer_call_count) { + (*release_buffer_call_count)++; +} + +TEST_F(SurfaceTest, Attach) { + gfx::Size buffer_size(256, 256); + scoped_ptr<Buffer> buffer( + new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size).Pass(), + GL_TEXTURE_2D)); + + // Set the release callback that will be run when buffer is no longer in use. + int release_buffer_call_count = 0; + buffer->set_release_callback( + base::Bind(&ReleaseBuffer, base::Unretained(&release_buffer_call_count))); + + scoped_ptr<Surface> surface1(new Surface); + scoped_ptr<Surface> surface2(new Surface); + + // Attach the buffer to surface1. + surface1->Attach(buffer.get()); + surface1->Commit(); + + // Attaching buffer to surface2 when it is already attached to surface1 + // should fail and buffer should remain attached to surface1. + surface2->Attach(buffer.get()); + surface2->Commit(); + + // Attach a null buffer to surface1, this should release the previously + // attached buffer. + surface1->Attach(nullptr); + surface1->Commit(); + ASSERT_EQ(release_buffer_call_count, 1); +} + +TEST_F(SurfaceTest, Damage) { + gfx::Size buffer_size(256, 256); + scoped_ptr<Buffer> buffer( + new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size).Pass(), + GL_TEXTURE_2D)); + scoped_ptr<Surface> surface(new Surface); + + // Attach the buffer to the surface. This will update the pending bounds of + // the surface to the buffer size. + surface->Attach(buffer.get()); + + // Mark area inside the bounds of the surface as damaged. This should result + // in pending damage. + surface->Damage(gfx::Rect(0, 0, 10, 10)); + EXPECT_TRUE(surface->HasPendingDamageForTesting()); +} + +void SetFrameTime(base::TimeTicks* result, base::TimeTicks frame_time) { + *result = frame_time; +} + +TEST_F(SurfaceTest, RequestFrameCallback) { + scoped_ptr<Surface> surface(new Surface); + + base::TimeTicks frame_time; + surface->RequestFrameCallback( + base::Bind(&SetFrameTime, base::Unretained(&frame_time))); + surface->Commit(); + + // Callback should not run synchronously. + EXPECT_TRUE(frame_time.is_null()); +} + +TEST_F(SurfaceTest, SetOpaqueRegion) { + scoped_ptr<Surface> surface(new Surface); + + // Setting a non-empty opaque region should succeed. + surface->SetOpaqueRegion(SkRegion(SkIRect::MakeWH(256, 256))); + + // Setting an empty opaque region should succeed. + surface->SetOpaqueRegion(SkRegion(SkIRect::MakeEmpty())); +} + +TEST_F(SurfaceTest, Commit) { + scoped_ptr<Surface> surface(new Surface); + + // Calling commit without a buffer should succeed. + surface->Commit(); +} + +} // namespace +} // namespace exo diff --git a/components/exo/test/exo_test_base.cc b/components/exo/test/exo_test_base.cc new file mode 100644 index 0000000..c988048 --- /dev/null +++ b/components/exo/test/exo_test_base.cc @@ -0,0 +1,28 @@ +// Copyright 2015 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 "components/exo/test/exo_test_base.h" + +#include "components/exo/test/exo_test_helper.h" + +namespace exo { +namespace test { + +//////////////////////////////////////////////////////////////////////////////// +// ExoTestBase, public: + +ExoTestBase::ExoTestBase() : exo_test_helper_(new ExoTestHelper) {} + +ExoTestBase::~ExoTestBase() {} + +void ExoTestBase::SetUp() { + AshTestBase::SetUp(); +} + +void ExoTestBase::TearDown() { + AshTestBase::TearDown(); +} + +} // namespace test +} // namespace exo diff --git a/components/exo/test/exo_test_base.h b/components/exo/test/exo_test_base.h new file mode 100644 index 0000000..98a1f86 --- /dev/null +++ b/components/exo/test/exo_test_base.h @@ -0,0 +1,36 @@ +// Copyright 2015 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 COMPONENTS_EXO_TEST_EXO_TEST_BASE_H_ +#define COMPONENTS_EXO_TEST_EXO_TEST_BASE_H_ + +#include "ash/test/ash_test_base.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace exo { +namespace test { +class ExoTestHelper; + +class ExoTestBase : public ash::test::AshTestBase { + public: + ExoTestBase(); + ~ExoTestBase() override; + + // Overridden from testing::Test: + void SetUp() override; + void TearDown() override; + + ExoTestHelper* exo_test_helper() { return exo_test_helper_.get(); } + + private: + scoped_ptr<ExoTestHelper> exo_test_helper_; + + DISALLOW_COPY_AND_ASSIGN(ExoTestBase); +}; + +} // namespace test +} // namespace exo + +#endif // COMPONENTS_EXO_TEST_EXO_TEST_BASE_H_ diff --git a/components/exo/test/exo_test_helper.cc b/components/exo/test/exo_test_helper.cc new file mode 100644 index 0000000..84d580a --- /dev/null +++ b/components/exo/test/exo_test_helper.cc @@ -0,0 +1,31 @@ +// Copyright 2015 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 "components/exo/test/exo_test_helper.h" + +#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" +#include "ui/aura/env.h" +#include "ui/compositor/compositor.h" + +namespace exo { +namespace test { + +//////////////////////////////////////////////////////////////////////////////// +// ExoTestHelper, public: + +ExoTestHelper::ExoTestHelper() {} + +ExoTestHelper::~ExoTestHelper() {} + +scoped_ptr<gfx::GpuMemoryBuffer> ExoTestHelper::CreateGpuMemoryBuffer( + const gfx::Size& size) { + return aura::Env::GetInstance() + ->context_factory() + ->GetGpuMemoryBufferManager() + ->AllocateGpuMemoryBuffer(size, gfx::BufferFormat::RGBA_8888, + gfx::BufferUsage::GPU_READ); +} + +} // namespace test +} // namespace exo diff --git a/components/exo/test/exo_test_helper.h b/components/exo/test/exo_test_helper.h new file mode 100644 index 0000000..2044405 --- /dev/null +++ b/components/exo/test/exo_test_helper.h @@ -0,0 +1,36 @@ +// Copyright 2015 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 COMPONENTS_EXO_TEST_EXO_TEST_HELPER_H_ +#define COMPONENTS_EXO_TEST_EXO_TEST_HELPER_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "ui/gfx/geometry/size.h" + +namespace gfx { +class GpuMemoryBuffer; +} + +namespace exo { +namespace test { + +// A helper class that does common initialization required for Exosphere. +class ExoTestHelper { + public: + ExoTestHelper(); + ~ExoTestHelper(); + + // Creates a GpuMemoryBuffer instance that can be used for tests. + scoped_ptr<gfx::GpuMemoryBuffer> CreateGpuMemoryBuffer(const gfx::Size& size); + + private: + DISALLOW_COPY_AND_ASSIGN(ExoTestHelper); +}; + +} // namespace test +} // namespace exo + +#endif // COMPONENTS_EXO_TEST_EXO_TEST_HELPER_H_ diff --git a/components/exo/test/run_all_unittests.cc b/components/exo/test/run_all_unittests.cc new file mode 100644 index 0000000..e69d387 --- /dev/null +++ b/components/exo/test/run_all_unittests.cc @@ -0,0 +1,15 @@ +// Copyright 2015 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 "ash/test/test_suite.h" +#include "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" + +int main(int argc, char** argv) { + ash::test::AuraShellTestSuite test_suite(argc, argv); + + return base::LaunchUnitTestsSerially( + argc, argv, base::Bind(&ash::test::AuraShellTestSuite::Run, + base::Unretained(&test_suite))); +} diff --git a/components/exo/wayland/BUILD.gn b/components/exo/wayland/BUILD.gn new file mode 100644 index 0000000..2041108 --- /dev/null +++ b/components/exo/wayland/BUILD.gn @@ -0,0 +1,35 @@ +# Copyright 2015 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. + +source_set("wayland") { + sources = [ + "scoped_wl_types.cc", + "scoped_wl_types.h", + "server.cc", + "server.h", + ] + + defines = [ "EXO_IMPLEMENTATION" ] + + deps = [ + "//base", + "//components/exo", + "//skia", + "//third_party/wayland:wayland_server", + ] +} + +source_set("unit_tests") { + testonly = true + + sources = [ + "server_unittest.cc", + ] + + deps = [ + "//testing/gtest", + "//third_party/wayland:wayland_client", + ":wayland", + ] +} diff --git a/components/exo/wayland/DEPS b/components/exo/wayland/DEPS new file mode 100644 index 0000000..067074d --- /dev/null +++ b/components/exo/wayland/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/wayland/include", +] diff --git a/components/exo/wayland/scoped_wl_types.cc b/components/exo/wayland/scoped_wl_types.cc new file mode 100644 index 0000000..7050cee --- /dev/null +++ b/components/exo/wayland/scoped_wl_types.cc @@ -0,0 +1,20 @@ +// Copyright 2015 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 "components/exo/wayland/scoped_wl_types.h" + +#include <wayland-server-core.h> + +namespace exo { +namespace wayland { +namespace internal { + +// static +void ScopedWLDisplayTraits::Free(wl_display* display) { + wl_display_destroy(display); +} + +} // namespace internal +} // namespace wayland +} // namespace exo diff --git a/components/exo/wayland/scoped_wl_types.h b/components/exo/wayland/scoped_wl_types.h new file mode 100644 index 0000000..4ee819d --- /dev/null +++ b/components/exo/wayland/scoped_wl_types.h @@ -0,0 +1,31 @@ +// Copyright 2015 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 COMPONENTS_EXO_WAYLAND_SCOPED_WL_TYPES_H_ +#define COMPONENTS_EXO_WAYLAND_SCOPED_WL_TYPES_H_ + +#include "base/scoped_generic.h" + +extern "C" { +struct wl_display; +} + +namespace exo { +namespace wayland { +namespace internal { + +struct ScopedWLDisplayTraits { + static wl_display* InvalidValue() { return nullptr; } + static void Free(wl_display* display); +}; + +} // namespace internal + +using ScopedWLDisplay = + base::ScopedGeneric<wl_display*, internal::ScopedWLDisplayTraits>; + +} // namespace wayland +} // namespace exo + +#endif // COMPONENTS_EXO_WAYLAND_SCOPED_WL_TYPES_H_ diff --git a/components/exo/wayland/server.cc b/components/exo/wayland/server.cc new file mode 100644 index 0000000..d5efe22 --- /dev/null +++ b/components/exo/wayland/server.cc @@ -0,0 +1,534 @@ +// Copyright 2015 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 "components/exo/wayland/server.h" + +#include <wayland-server-core.h> +#include <wayland-server-protocol-core.h> + +#include <algorithm> + +#include "base/bind.h" +#include "base/cancelable_callback.h" +#include "base/strings/utf_string_conversions.h" +#include "components/exo/buffer.h" +#include "components/exo/display.h" +#include "components/exo/shared_memory.h" +#include "components/exo/shell_surface.h" +#include "components/exo/surface.h" +#include "third_party/skia/include/core/SkRegion.h" + +namespace exo { +namespace wayland { +namespace { + +template <class T> +T* GetUserDataAs(wl_resource* resource) { + return static_cast<T*>(wl_resource_get_user_data(resource)); +} + +template <class T> +scoped_ptr<T> TakeUserDataAs(wl_resource* resource) { + scoped_ptr<T> user_data = make_scoped_ptr(GetUserDataAs<T>(resource)); + wl_resource_set_user_data(resource, nullptr); + return user_data.Pass(); +} + +template <class T> +void DestroyUserData(wl_resource* resource) { + TakeUserDataAs<T>(resource); +} + +template <class T> +void SetImplementation(wl_resource* resource, + const void* implementation, + scoped_ptr<T> user_data) { + wl_resource_set_implementation(resource, implementation, user_data.release(), + DestroyUserData<T>); +} + +//////////////////////////////////////////////////////////////////////////////// +// wl_buffer_interface: + +void buffer_destroy(wl_client* client, wl_resource* resource) { + wl_resource_destroy(resource); +} + +const struct wl_buffer_interface buffer_implementation = {buffer_destroy}; + +//////////////////////////////////////////////////////////////////////////////// +// wl_surface_interface: + +void surface_destroy(wl_client* client, wl_resource* resource) { + wl_resource_destroy(resource); +} + +void surface_attach(wl_client* client, + wl_resource* resource, + wl_resource* buffer, + int32_t x, + int32_t y) { + // TODO(reveman): Implement buffer offset support. + if (x || y) { + wl_resource_post_no_memory(resource); + return; + } + + GetUserDataAs<Surface>(resource) + ->Attach(buffer ? GetUserDataAs<Buffer>(buffer) : nullptr); +} + +void surface_damage(wl_client* client, + wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + GetUserDataAs<Surface>(resource)->Damage(gfx::Rect(x, y, width, height)); +} + +void handle_surface_frame_callback(wl_resource* resource, + base::TimeTicks frame_time) { + if (!frame_time.is_null()) { + wl_callback_send_done(resource, + (frame_time - base::TimeTicks()).InMilliseconds()); + // TODO(reveman): Remove this potentially blocking flush and instead watch + // the file descriptor to be ready for write without blocking. + wl_client_flush(wl_resource_get_client(resource)); + } + wl_resource_destroy(resource); +} + +void surface_frame(wl_client* client, + wl_resource* resource, + uint32_t callback) { + wl_resource* callback_resource = + wl_resource_create(client, &wl_callback_interface, 1, callback); + if (!callback_resource) { + wl_resource_post_no_memory(resource); + return; + } + + // base::Unretained is safe as the resource owns the callback. + scoped_ptr<base::CancelableCallback<void(base::TimeTicks)>> + cancelable_callback(new base::CancelableCallback<void(base::TimeTicks)>( + base::Bind(&handle_surface_frame_callback, + base::Unretained(callback_resource)))); + + GetUserDataAs<Surface>(resource) + ->RequestFrameCallback(cancelable_callback->callback()); + + SetImplementation(callback_resource, nullptr, cancelable_callback.Pass()); +} + +void surface_set_opaque_region(wl_client* client, + wl_resource* resource, + wl_resource* region_resource) { + GetUserDataAs<Surface>(resource)->SetOpaqueRegion( + region_resource ? *GetUserDataAs<SkRegion>(region_resource) + : SkRegion(SkIRect::MakeEmpty())); +} + +void surface_set_input_region(wl_client* client, + wl_resource* resource, + wl_resource* region_resource) { + NOTIMPLEMENTED(); +} + +void surface_commit(wl_client* client, wl_resource* resource) { + GetUserDataAs<Surface>(resource)->Commit(); +} + +void surface_set_buffer_transform(wl_client* client, + wl_resource* resource, + int transform) { + NOTIMPLEMENTED(); +} + +void surface_set_buffer_scale(wl_client* client, + wl_resource* resource, + int32_t scale) { + NOTIMPLEMENTED(); +} + +const struct wl_surface_interface surface_implementation = { + surface_destroy, + surface_attach, + surface_damage, + surface_frame, + surface_set_opaque_region, + surface_set_input_region, + surface_commit, + surface_set_buffer_transform, + surface_set_buffer_scale}; + +//////////////////////////////////////////////////////////////////////////////// +// wl_region_interface: + +void region_destroy(wl_client* client, wl_resource* resource) { + wl_resource_destroy(resource); +} + +void region_add(wl_client* client, + wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + GetUserDataAs<SkRegion>(resource) + ->op(SkIRect::MakeXYWH(x, y, width, height), SkRegion::kUnion_Op); +} + +static void region_subtract(wl_client* client, + wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + GetUserDataAs<SkRegion>(resource) + ->op(SkIRect::MakeXYWH(x, y, width, height), SkRegion::kDifference_Op); +} + +const struct wl_region_interface region_implementation = { + region_destroy, region_add, region_subtract}; + +//////////////////////////////////////////////////////////////////////////////// +// wl_compositor_interface: + +void compositor_create_surface(wl_client* client, + wl_resource* resource, + uint32_t id) { + scoped_ptr<Surface> surface = + GetUserDataAs<Display>(resource)->CreateSurface(); + DCHECK(surface); + + wl_resource* surface_resource = wl_resource_create( + client, &wl_surface_interface, wl_resource_get_version(resource), id); + if (!surface_resource) { + wl_resource_post_no_memory(resource); + return; + } + + SetImplementation(surface_resource, &surface_implementation, surface.Pass()); +} + +void compositor_create_region(wl_client* client, + wl_resource* resource, + uint32_t id) { + scoped_ptr<SkRegion> region(new SkRegion); + + wl_resource* region_resource = + wl_resource_create(client, &wl_region_interface, 1, id); + if (!region_resource) { + wl_resource_post_no_memory(resource); + return; + } + + SetImplementation(region_resource, ®ion_implementation, region.Pass()); +} + +const struct wl_compositor_interface compositor_implementation = { + compositor_create_surface, compositor_create_region}; + +const uint32_t compositor_version = 3; + +void bind_compositor(wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + wl_resource* resource = + wl_resource_create(client, &wl_compositor_interface, + std::min(version, compositor_version), id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &compositor_implementation, data, + nullptr); +} + +//////////////////////////////////////////////////////////////////////////////// +// wl_shm_pool_interface: + +const struct shm_supported_format { + uint32_t shm_format; + gfx::BufferFormat buffer_format; +} shm_supported_formats[] = { + {WL_SHM_FORMAT_XBGR8888, gfx::BufferFormat::RGBX_8888}, + {WL_SHM_FORMAT_ABGR8888, gfx::BufferFormat::RGBA_8888}, + {WL_SHM_FORMAT_XRGB8888, gfx::BufferFormat::BGRX_8888}, + {WL_SHM_FORMAT_ARGB8888, gfx::BufferFormat::BGRA_8888}}; + +void shm_pool_create_buffer(wl_client* client, + wl_resource* resource, + uint32_t id, + int32_t offset, + int32_t width, + int32_t height, + int32_t stride, + uint32_t format) { + const auto supported_format = + std::find_if(shm_supported_formats, + shm_supported_formats + arraysize(shm_supported_formats), + [format](const shm_supported_format& supported_format) { + return supported_format.shm_format == format; + }); + if (supported_format == + (shm_supported_formats + arraysize(shm_supported_formats))) { + wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FORMAT, + "invalid format 0x%x", format); + return; + } + + if (offset < 0) { + wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FORMAT, + "invalid offset %d", offset); + return; + } + + scoped_ptr<Buffer> buffer = + GetUserDataAs<SharedMemory>(resource) + ->CreateBuffer(gfx::Size(width, height), + supported_format->buffer_format, offset, stride); + if (!buffer) { + wl_resource_post_no_memory(resource); + return; + } + + wl_resource* buffer_resource = + wl_resource_create(client, &wl_buffer_interface, 1, id); + if (!buffer_resource) { + wl_resource_post_no_memory(resource); + return; + } + + buffer->set_release_callback( + base::Bind(&wl_buffer_send_release, base::Unretained(buffer_resource))); + + SetImplementation(buffer_resource, &buffer_implementation, buffer.Pass()); +} + +void shm_pool_destroy(wl_client* client, wl_resource* resource) { + wl_resource_destroy(resource); +} + +void shm_pool_resize(wl_client* client, wl_resource* resource, int32_t size) { + // Nothing to do here. +} + +const struct wl_shm_pool_interface shm_pool_implementation = { + shm_pool_create_buffer, shm_pool_destroy, shm_pool_resize}; + +//////////////////////////////////////////////////////////////////////////////// +// wl_shm_interface: + +void shm_create_pool(wl_client* client, + wl_resource* resource, + uint32_t id, + int fd, + int32_t size) { + scoped_ptr<SharedMemory> shared_memory = + GetUserDataAs<Display>(resource) + ->CreateSharedMemory(base::FileDescriptor(fd, true), size); + if (!shared_memory) { + wl_resource_post_no_memory(resource); + return; + } + + wl_resource* shm_pool_resource = + wl_resource_create(client, &wl_shm_pool_interface, 1, id); + if (!shm_pool_resource) { + wl_resource_post_no_memory(resource); + return; + } + + SetImplementation(shm_pool_resource, &shm_pool_implementation, + shared_memory.Pass()); +} + +const struct wl_shm_interface shm_implementation = {shm_create_pool}; + +void bind_shm(wl_client* client, void* data, uint32_t version, uint32_t id) { + wl_resource* resource = wl_resource_create(client, &wl_shm_interface, 1, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &shm_implementation, data, nullptr); + + for (const auto& supported_format : shm_supported_formats) + wl_shm_send_format(resource, supported_format.shm_format); +} + +//////////////////////////////////////////////////////////////////////////////// +// wl_shell_surface_interface: + +void shell_surface_pong(wl_client* client, + wl_resource* resource, + uint32_t serial) { + NOTIMPLEMENTED(); +} + +void shell_surface_move(wl_client* client, + wl_resource* resource, + wl_resource* seat_resource, + uint32_t serial) { + NOTIMPLEMENTED(); +} + +void shell_surface_resize(wl_client* client, + wl_resource* resource, + wl_resource* seat_resource, + uint32_t serial, + uint32_t edges) { + NOTIMPLEMENTED(); +} + +void shell_surface_set_toplevel(wl_client* client, wl_resource* resource) { + GetUserDataAs<ShellSurface>(resource)->SetToplevel(); +} + +void shell_surface_set_transient(wl_client* client, + wl_resource* resource, + wl_resource* parent_resource, + int x, + int y, + uint32_t flags) { + NOTIMPLEMENTED(); +} + +void shell_surface_set_fullscreen(wl_client* client, + wl_resource* resource, + uint32_t method, + uint32_t framerate, + wl_resource* output_resource) { + GetUserDataAs<ShellSurface>(resource)->SetFullscreen(true); +} + +void shell_surface_set_popup(wl_client* client, + wl_resource* resource, + wl_resource* seat_resource, + uint32_t serial, + wl_resource* parent_resource, + int32_t x, + int32_t y, + uint32_t flags) { + NOTIMPLEMENTED(); +} + +void shell_surface_set_maximized(wl_client* client, + wl_resource* resource, + wl_resource* output_resource) { + NOTIMPLEMENTED(); +} + +void shell_surface_set_title(wl_client* client, + wl_resource* resource, + const char* title) { + GetUserDataAs<ShellSurface>(resource) + ->SetTitle(base::string16(base::ASCIIToUTF16(title))); +} + +void shell_surface_set_class(wl_client* client, + wl_resource* resource, + const char* clazz) { + NOTIMPLEMENTED(); +} + +const struct wl_shell_surface_interface shell_surface_implementation = { + shell_surface_pong, shell_surface_move, + shell_surface_resize, shell_surface_set_toplevel, + shell_surface_set_transient, shell_surface_set_fullscreen, + shell_surface_set_popup, shell_surface_set_maximized, + shell_surface_set_title, shell_surface_set_class}; + +//////////////////////////////////////////////////////////////////////////////// +// wl_shell_interface: + +void shell_get_shell_surface(wl_client* client, + wl_resource* resource, + uint32_t id, + wl_resource* surface) { + scoped_ptr<ShellSurface> shell_surface = + GetUserDataAs<Display>(resource) + ->CreateShellSurface(GetUserDataAs<Surface>(surface)); + if (!shell_surface) { + wl_resource_post_no_memory(resource); + return; + } + + wl_resource* shell_surface_resource = + wl_resource_create(client, &wl_shell_surface_interface, 1, id); + if (!shell_surface_resource) { + wl_resource_post_no_memory(resource); + return; + } + + SetImplementation(shell_surface_resource, &shell_surface_implementation, + shell_surface.Pass()); +} + +const struct wl_shell_interface shell_implementation = { + shell_get_shell_surface}; + +void bind_shell(wl_client* client, void* data, uint32_t version, uint32_t id) { + wl_resource* resource = + wl_resource_create(client, &wl_shell_interface, 1, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &shell_implementation, data, + nullptr); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// Server, public: + +Server::Server(Display* display) + : display_(display), wl_display_(wl_display_create()) { + wl_global_create(wl_display_.get(), &wl_compositor_interface, + compositor_version, display_, bind_compositor); + wl_global_create(wl_display_.get(), &wl_shm_interface, 1, display_, bind_shm); + wl_global_create(wl_display_.get(), &wl_shell_interface, 1, display_, + bind_shell); +} + +Server::~Server() {} + +// static +scoped_ptr<Server> Server::Create(Display* display) { + scoped_ptr<Server> server(new Server(display)); + int rv = wl_display_add_socket(server->wl_display_.get(), nullptr); + DCHECK_EQ(rv, 0) << "wl_display_add_socket failed: " << rv; + return server; +} + +bool Server::AddSocket(const std::string name) { + DCHECK(!name.empty()); + return !wl_display_add_socket(wl_display_.get(), name.c_str()); +} + +int Server::GetFileDescriptor() const { + wl_event_loop* event_loop = wl_display_get_event_loop(wl_display_.get()); + DCHECK(event_loop); + return wl_event_loop_get_fd(event_loop); +} + +void Server::Dispatch(base::TimeDelta timeout) { + wl_event_loop* event_loop = wl_display_get_event_loop(wl_display_.get()); + DCHECK(event_loop); + wl_event_loop_dispatch(event_loop, timeout.InMilliseconds()); +} + +void Server::Flush() { + wl_display_flush_clients(wl_display_.get()); +} + +} // namespace wayland +} // namespace exo diff --git a/components/exo/wayland/server.h b/components/exo/wayland/server.h new file mode 100644 index 0000000..34280bb --- /dev/null +++ b/components/exo/wayland/server.h @@ -0,0 +1,57 @@ +// Copyright 2015 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 COMPONENTS_EXO_WAYLAND_SERVER_H_ +#define COMPONENTS_EXO_WAYLAND_SERVER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "components/exo/wayland/scoped_wl_types.h" + +namespace exo { +class Display; + +namespace wayland { + +// This class is a thin wrapper around a Wayland display server. All Wayland +// requests are dispatched into the given Exospere display. +class Server { + public: + explicit Server(Display* display); + ~Server(); + + // Creates a Wayland display server that clients can connect to using the + // default socket name. + static scoped_ptr<Server> Create(Display* display); + + // This adds a Unix socket to the Wayland display server which can be used + // by clients to connect to the display server. + bool AddSocket(const std::string name); + + // Returns the file descriptor associated with the server. + int GetFileDescriptor() const; + + // This function dispatches events. This must be called on a thread for + // which it's safe to access the Exospere display that this server was + // created for. The |timeout| argument specifies the amount of time that + // Dispatch() should block waiting for the file descriptor to become ready. + void Dispatch(base::TimeDelta timeout); + + // Send all buffered events to the clients. + void Flush(); + + private: + Display* const display_; + ScopedWLDisplay wl_display_; + + DISALLOW_COPY_AND_ASSIGN(Server); +}; + +} // namespace wayland +} // namespace exo + +#endif // COMPONENTS_EXO_WAYLAND_SERVER_H_ diff --git a/components/exo/wayland/server_unittest.cc b/components/exo/wayland/server_unittest.cc new file mode 100644 index 0000000..adc1dd4 --- /dev/null +++ b/components/exo/wayland/server_unittest.cc @@ -0,0 +1,97 @@ +// Copyright 2015 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 <wayland-client-core.h> + +#include "base/atomic_sequence_num.h" +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/process/process_handle.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread.h" +#include "components/exo/display.h" +#include "components/exo/wayland/server.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace exo { +namespace wayland { +namespace { + +base::StaticAtomicSequenceNumber g_next_socket_id; + +std::string GetUniqueSocketName() { + return base::StringPrintf("wayland-test-%d-%d", base::GetCurrentProcId(), + g_next_socket_id.GetNext()); +} + +TEST(ServerTest, AddSocket) { + scoped_ptr<Display> display(new Display); + scoped_ptr<Server> server(new Server(display.get())); + + // Check that calling AddSocket() with a unique socket name succeeds. + bool rv = server->AddSocket(GetUniqueSocketName()); + EXPECT_TRUE(rv); +} + +TEST(ServerTest, GetFileDescriptor) { + scoped_ptr<Display> display(new Display); + scoped_ptr<Server> server(new Server(display.get())); + + bool rv = server->AddSocket(GetUniqueSocketName()); + EXPECT_TRUE(rv); + + // Check that the returned file descriptor is valid. + int fd = server->GetFileDescriptor(); + DCHECK_GE(fd, 0); +} + +void ConnectToServer(const std::string socket_name, + bool* connected_to_server, + base::WaitableEvent* event) { + wl_display* display = wl_display_connect(socket_name.c_str()); + *connected_to_server = !!display; + event->Signal(); + wl_display_disconnect(display); +} + +TEST(ServerTest, Dispatch) { + scoped_ptr<Display> display(new Display); + scoped_ptr<Server> server(new Server(display.get())); + + std::string socket_name = GetUniqueSocketName(); + bool rv = server->AddSocket(socket_name); + EXPECT_TRUE(rv); + + base::Thread client("client-" + socket_name); + ASSERT_TRUE(client.Start()); + + // Post a task that connects server on the created thread. + bool connected_to_server = false; + base::WaitableEvent event(false, false); + client.task_runner()->PostTask( + FROM_HERE, + base::Bind(&ConnectToServer, socket_name, &connected_to_server, &event)); + + // Call Dispatch() with a 5 second timeout. + server->Dispatch(base::TimeDelta::FromSeconds(5)); + + // Check if client thread managed to connect to server. + event.Wait(); + EXPECT_TRUE(connected_to_server); +} + +TEST(ServerTest, Flush) { + scoped_ptr<Display> display(new Display); + scoped_ptr<Server> server(new Server(display.get())); + + bool rv = server->AddSocket(GetUniqueSocketName()); + EXPECT_TRUE(rv); + + // Just call Flush to check that it doesn't have any bad side-effects. + server->Flush(); +} + +} // namespace +} // namespace wayland +} // namespace exo |
