Start building the unit test to see what else we need to build
Test: get a lot of link errors
Bug: 171711491
Change-Id: I12699a802a5415e21694a88678e47bba3a1ec439
diff --git a/stream-servers/CMakeLists.txt b/stream-servers/CMakeLists.txt
index cb5a8a5..394aaba 100644
--- a/stream-servers/CMakeLists.txt
+++ b/stream-servers/CMakeLists.txt
@@ -60,7 +60,7 @@
${gfxstream_backend-platform-sources})
target_link_libraries(
gfxstream_backend
- PRIVATE
+ PUBLIC
gfxstream-host-common
gfxstream-base
OpenGLESDispatch
@@ -71,10 +71,25 @@
apigen-codec-common)
target_include_directories(
gfxstream_backend
- PRIVATE
+ PUBLIC
${GFXSTREAM_REPO_ROOT}
${GFXSTREAM_REPO_ROOT}/include
${GFXSTREAM_REPO_ROOT}/stream-servers
${GFXSTREAM_REPO_ROOT}/stream-servers/apigen-codec-common
${GFXSTREAM_REPO_ROOT}/stream-servers/vulkan)
+# Testing libraries
+add_subdirectory(testlibs)
+
+# Backend unit tests
+add_executable(
+ gfxstream_backend_unittests
+ gfxstream_unittest.cpp)
+target_link_libraries(
+ gfxstream_backend_unittests
+ PRIVATE
+ OSWindow
+ gfxstream_backend
+ gtest_main)
+
+
diff --git a/stream-servers/GfxStreamBackend.h b/stream-servers/GfxStreamBackend.h
index 87585d9..00a2908 100644
--- a/stream-servers/GfxStreamBackend.h
+++ b/stream-servers/GfxStreamBackend.h
@@ -1,6 +1,6 @@
extern "C" {
-#include "hw/misc/goldfish_pipe.h"
-#include "hw/virtio/virtio-goldfish-pipe.h"
+#include "host-common/goldfish_pipe.h"
+#include "virtio-gpu-gfxstream-renderer.h"
#include "virgl_hw.h"
} // extern "C"
@@ -38,4 +38,4 @@
int32_t fb_width,
int32_t fb_height);
-extern "C" VG_EXPORT void gfxstream_backend_teardown(void);
\ No newline at end of file
+extern "C" VG_EXPORT void gfxstream_backend_teardown(void);
diff --git a/stream-servers/gfxstream_unittest.cpp b/stream-servers/gfxstream_unittest.cpp
new file mode 100644
index 0000000..6a33b4c
--- /dev/null
+++ b/stream-servers/gfxstream_unittest.cpp
@@ -0,0 +1,123 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "GfxStreamBackend.h"
+#include "OSWindow.h"
+#include "base/System.h"
+
+class GfxStreamBackendTest : public ::testing::Test {
+private:
+ static void sWriteFence(void* cookie, uint32_t fence) {
+ uint32_t current = *(uint32_t*)cookie;
+ if (current < fence)
+ *(uint32_t*)(cookie) = fence;
+ }
+
+protected:
+ uint32_t cookie;
+ static const bool useWindow;
+ struct virgl_renderer_callbacks callbacks;
+ static constexpr uint32_t width = 256;
+ static constexpr uint32_t height = 256;
+ static std::unique_ptr<OSWindow> window;
+
+ GfxStreamBackendTest()
+ : cookie(0),
+ callbacks({
+ 0,
+ sWriteFence,
+ 0,
+ 0,
+ 0,
+ }) {}
+
+ static void SetUpTestSuite() {
+ if (useWindow) {
+ window.reset(CreateOSWindow());
+ }
+ }
+
+ static void TearDownTestSuite() { window.reset(nullptr); }
+
+ void SetUp() override {
+ android::base::setEnvironmentVariable("ANDROID_GFXSTREAM_EGL", "1");
+ if (useWindow) {
+ window->initialize("GfxStreamBackendTestWindow", width, height);
+ window->setVisible(true);
+ window->messageLoop();
+ }
+ }
+
+ void TearDown() override {
+ if (useWindow) {
+ window->destroy();
+ }
+ gfxstream_backend_teardown();
+ }
+};
+
+std::unique_ptr<OSWindow> GfxStreamBackendTest::window = nullptr;
+
+const bool GfxStreamBackendTest::useWindow =
+ android::base::getEnvironmentVariable("ANDROID_EMU_TEST_WITH_WINDOW") == "1";
+
+TEST_F(GfxStreamBackendTest, Init) {
+ gfxstream_backend_init(width, height, 0, &cookie,
+ GFXSTREAM_RENDERER_FLAGS_USE_SURFACELESS_BIT |
+ GFXSTREAM_RENDERER_FLAGS_NO_VK_BIT,
+ &callbacks);
+}
+
+TEST_F(GfxStreamBackendTest, InitOpenGLWindow) {
+ if (!useWindow) {
+ return;
+ }
+ gfxstream_backend_init(width, height, 0, &cookie,
+ GFXSTREAM_RENDERER_FLAGS_NO_VK_BIT, &callbacks);
+ gfxstream_backend_setup_window(window->getFramebufferNativeWindow(), 0, 0,
+ width, height, width, height);
+}
+
+TEST_F(GfxStreamBackendTest, SimpleFlush) {
+ gfxstream_backend_init(width, height, 0, &cookie,
+ GFXSTREAM_RENDERER_FLAGS_USE_SURFACELESS_BIT |
+ GFXSTREAM_RENDERER_FLAGS_NO_VK_BIT,
+ &callbacks);
+
+ const uint32_t res_id = 8;
+ struct virgl_renderer_resource_create_args create_resource_args = {
+ .handle = res_id,
+ .target = 2, // PIPE_TEXTURE_2D
+ .format = VIRGL_FORMAT_R8G8B8A8_UNORM,
+ .bind = VIRGL_BIND_SAMPLER_VIEW | VIRGL_BIND_SCANOUT |
+ VIRGL_BIND_SHARED,
+ .width = width,
+ .height = height,
+ .depth = 1,
+ .array_size = 1,
+ .last_level = 0,
+ .nr_samples = 0,
+ .flags = 0,
+ };
+ EXPECT_EQ(
+ pipe_virgl_renderer_resource_create(&create_resource_args, NULL, 0),
+ 0);
+ // R8G8B8A8 is used, so 4 bytes per pixel
+ auto fb = std::make_unique<uint32_t[]>(width * height);
+ EXPECT_NE(fb, nullptr);
+ stream_renderer_flush_resource_and_readback(res_id, 0, 0, width, height,
+ fb.get(), width * height);
+}
diff --git a/stream-servers/testlibs/CMakeLists.txt b/stream-servers/testlibs/CMakeLists.txt
new file mode 100644
index 0000000..770889c
--- /dev/null
+++ b/stream-servers/testlibs/CMakeLists.txt
@@ -0,0 +1,27 @@
+if (APPLE)
+ set(oswindow-platform-sources
+ osx/OSXWindow.mm)
+elseif (WIN32)
+ set(oswindow-platform-sources
+ windows/WindowsTimer.cpp
+ windows/Windows_system-utils.cpp
+ windows/win32/Win32Window.cpp)
+else()
+ set(oswindow-platform-sources
+ x11/X11Window.cpp)
+endif()
+
+add_library(
+ OSWindow
+ OSWindow.cpp
+ ${oswindow-platform-sources})
+target_link_libraries(
+ OSWindow
+ PRIVATE
+ gfxstream-base)
+target_include_directories(
+ OSWindow
+ PUBLIC
+ .
+ PRIVATE
+ ${GFXSTREAM_REPO_ROOT}/include)
diff --git a/stream-servers/testlibs/Event.h b/stream-servers/testlibs/Event.h
new file mode 100644
index 0000000..7dd9cb3
--- /dev/null
+++ b/stream-servers/testlibs/Event.h
@@ -0,0 +1,87 @@
+//
+// Copyright (c) 2014 The ANGLE Project 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 SAMPLE_UTIL_EVENT_H
+#define SAMPLE_UTIL_EVENT_H
+
+#include "keyboard.h"
+#include "mouse.h"
+
+class Event
+{
+ public:
+ struct MoveEvent
+ {
+ int X;
+ int Y;
+ };
+
+ struct SizeEvent
+ {
+ int Width;
+ int Height;
+ };
+
+ struct KeyEvent
+ {
+ Key Code;
+ bool Alt;
+ bool Control;
+ bool Shift;
+ bool System;
+ };
+
+ struct MouseMoveEvent
+ {
+ int X;
+ int Y;
+ };
+
+ struct MouseButtonEvent
+ {
+ MouseButton Button;
+ int X;
+ int Y;
+ };
+
+ struct MouseWheelEvent
+ {
+ int Delta;
+ };
+
+ enum EventType
+ {
+ EVENT_CLOSED, // The window requested to be closed
+ EVENT_MOVED, // The window has moved
+ EVENT_RESIZED, // The window was resized
+ EVENT_LOST_FOCUS, // The window lost the focus
+ EVENT_GAINED_FOCUS, // The window gained the focus
+ EVENT_TEXT_ENTERED, // A character was entered
+ EVENT_KEY_PRESSED, // A key was pressed
+ EVENT_KEY_RELEASED, // A key was released
+ EVENT_MOUSE_WHEEL_MOVED, // The mouse wheel was scrolled
+ EVENT_MOUSE_BUTTON_PRESSED, // A mouse button was pressed
+ EVENT_MOUSE_BUTTON_RELEASED, // A mouse button was released
+ EVENT_MOUSE_MOVED, // The mouse cursor moved
+ EVENT_MOUSE_ENTERED, // The mouse cursor entered the area of the window
+ EVENT_MOUSE_LEFT, // The mouse cursor left the area of the window
+ EVENT_TEST, // Event for testing purposes
+ };
+
+ EventType Type;
+
+ union
+ {
+ MoveEvent Move; // Move event parameters
+ SizeEvent Size; // Size event parameters
+ KeyEvent Key; // Key event parameters
+ MouseMoveEvent MouseMove; // Mouse move event parameters
+ MouseButtonEvent MouseButton; // Mouse button event parameters
+ MouseWheelEvent MouseWheel; // Mouse wheel event parameters
+ };
+};
+
+#endif // SAMPLE_UTIL_EVENT_H
diff --git a/stream-servers/testlibs/OSWindow.cpp b/stream-servers/testlibs/OSWindow.cpp
new file mode 100644
index 0000000..631c4c0
--- /dev/null
+++ b/stream-servers/testlibs/OSWindow.cpp
@@ -0,0 +1,326 @@
+//
+// Copyright (c) 2014 The ANGLE Project 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 "OSWindow.h"
+
+#include <iostream>
+#include <sstream>
+
+#ifndef DEBUG_EVENTS
+#define DEBUG_EVENTS 0
+#endif
+
+#if DEBUG_EVENTS
+static const char *MouseButtonName(MouseButton button)
+{
+ switch (button)
+ {
+ case MOUSEBUTTON_UNKNOWN:
+ return "Unknown";
+ case MOUSEBUTTON_LEFT:
+ return "Left";
+ case MOUSEBUTTON_RIGHT:
+ return "Right";
+ case MOUSEBUTTON_MIDDLE:
+ return "Middle";
+ case MOUSEBUTTON_BUTTON4:
+ return "Button4";
+ case MOUSEBUTTON_BUTTON5:
+ return "Button5";
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+static const char *KeyName(Key key)
+{
+ switch (key)
+ {
+ case KEY_UNKNOWN: return "Unknown";
+ case KEY_A: return "A";
+ case KEY_B: return "B";
+ case KEY_C: return "C";
+ case KEY_D: return "D";
+ case KEY_E: return "E";
+ case KEY_F: return "F";
+ case KEY_G: return "G";
+ case KEY_H: return "H";
+ case KEY_I: return "I";
+ case KEY_J: return "J";
+ case KEY_K: return "K";
+ case KEY_L: return "L";
+ case KEY_M: return "M";
+ case KEY_N: return "N";
+ case KEY_O: return "O";
+ case KEY_P: return "P";
+ case KEY_Q: return "Q";
+ case KEY_R: return "R";
+ case KEY_S: return "S";
+ case KEY_T: return "T";
+ case KEY_U: return "U";
+ case KEY_V: return "V";
+ case KEY_W: return "W";
+ case KEY_X: return "X";
+ case KEY_Y: return "Y";
+ case KEY_Z: return "Z";
+ case KEY_NUM0: return "Num0";
+ case KEY_NUM1: return "Num1";
+ case KEY_NUM2: return "Num2";
+ case KEY_NUM3: return "Num3";
+ case KEY_NUM4: return "Num4";
+ case KEY_NUM5: return "Num5";
+ case KEY_NUM6: return "Num6";
+ case KEY_NUM7: return "Num7";
+ case KEY_NUM8: return "Num8";
+ case KEY_NUM9: return "Num9";
+ case KEY_ESCAPE: return "Escape";
+ case KEY_LCONTROL: return "Left Control";
+ case KEY_LSHIFT: return "Left Shift";
+ case KEY_LALT: return "Left Alt";
+ case KEY_LSYSTEM: return "Left System";
+ case KEY_RCONTROL: return "Right Control";
+ case KEY_RSHIFT: return "Right Shift";
+ case KEY_RALT: return "Right Alt";
+ case KEY_RSYSTEM: return "Right System";
+ case KEY_MENU: return "Menu";
+ case KEY_LBRACKET: return "Left Bracket";
+ case KEY_RBRACKET: return "Right Bracket";
+ case KEY_SEMICOLON: return "Semicolon";
+ case KEY_COMMA: return "Comma";
+ case KEY_PERIOD: return "Period";
+ case KEY_QUOTE: return "Quote";
+ case KEY_SLASH: return "Slash";
+ case KEY_BACKSLASH: return "Backslash";
+ case KEY_TILDE: return "Tilde";
+ case KEY_EQUAL: return "Equal";
+ case KEY_DASH: return "Dash";
+ case KEY_SPACE: return "Space";
+ case KEY_RETURN: return "Return";
+ case KEY_BACK: return "Back";
+ case KEY_TAB: return "Tab";
+ case KEY_PAGEUP: return "Page Up";
+ case KEY_PAGEDOWN: return "Page Down";
+ case KEY_END: return "End";
+ case KEY_HOME: return "Home";
+ case KEY_INSERT: return "Insert";
+ case KEY_DELETE: return "Delete";
+ case KEY_ADD: return "Add";
+ case KEY_SUBTRACT: return "Substract";
+ case KEY_MULTIPLY: return "Multiply";
+ case KEY_DIVIDE: return "Divide";
+ case KEY_LEFT: return "Left";
+ case KEY_RIGHT: return "Right";
+ case KEY_UP: return "Up";
+ case KEY_DOWN: return "Down";
+ case KEY_NUMPAD0: return "Numpad 0";
+ case KEY_NUMPAD1: return "Numpad 1";
+ case KEY_NUMPAD2: return "Numpad 2";
+ case KEY_NUMPAD3: return "Numpad 3";
+ case KEY_NUMPAD4: return "Numpad 4";
+ case KEY_NUMPAD5: return "Numpad 5";
+ case KEY_NUMPAD6: return "Numpad 6";
+ case KEY_NUMPAD7: return "Numpad 7";
+ case KEY_NUMPAD8: return "Numpad 8";
+ case KEY_NUMPAD9: return "Numpad 9";
+ case KEY_F1: return "F1";
+ case KEY_F2: return "F2";
+ case KEY_F3: return "F3";
+ case KEY_F4: return "F4";
+ case KEY_F5: return "F5";
+ case KEY_F6: return "F6";
+ case KEY_F7: return "F7";
+ case KEY_F8: return "F8";
+ case KEY_F9: return "F9";
+ case KEY_F10: return "F10";
+ case KEY_F11: return "F11";
+ case KEY_F12: return "F12";
+ case KEY_F13: return "F13";
+ case KEY_F14: return "F14";
+ case KEY_F15: return "F15";
+ case KEY_PAUSE: return "Pause";
+ default: return "Unknown Key";
+ }
+}
+
+static std::string KeyState(const Event::KeyEvent &event)
+{
+ if (event.Shift || event.Control || event.Alt || event.System)
+ {
+ std::ostringstream buffer;
+ buffer << " [";
+
+ if (event.Shift)
+ {
+ buffer << "Shift";
+ }
+ if (event.Control)
+ {
+ buffer << "Control";
+ }
+ if (event.Alt)
+ {
+ buffer << "Alt";
+ }
+ if (event.System)
+ {
+ buffer << "System";
+ }
+
+ buffer << "]";
+ return buffer.str();
+ }
+ return "";
+}
+
+static void PrintEvent(const Event& event)
+{
+ switch (event.Type)
+ {
+ case Event::EVENT_CLOSED:
+ std::cout << "Event: Window Closed" << std::endl;
+ break;
+ case Event::EVENT_MOVED:
+ std::cout << "Event: Window Moved (" << event.Move.X
+ << ", " << event.Move.Y << ")" << std::endl;
+ break;
+ case Event::EVENT_RESIZED:
+ std::cout << "Event: Window Resized (" << event.Size.Width
+ << ", " << event.Size.Height << ")" << std::endl;
+ break;
+ case Event::EVENT_LOST_FOCUS:
+ std::cout << "Event: Window Lost Focus" << std::endl;
+ break;
+ case Event::EVENT_GAINED_FOCUS:
+ std::cout << "Event: Window Gained Focus" << std::endl;
+ break;
+ case Event::EVENT_TEXT_ENTERED:
+ // TODO(cwallez) show the character
+ std::cout << "Event: Text Entered" << std::endl;
+ break;
+ case Event::EVENT_KEY_PRESSED:
+ std::cout << "Event: Key Pressed (" << KeyName(event.Key.Code) << KeyState(event.Key) << ")" << std::endl;
+ break;
+ case Event::EVENT_KEY_RELEASED:
+ std::cout << "Event: Key Released (" << KeyName(event.Key.Code) << KeyState(event.Key) << ")" << std::endl;
+ break;
+ case Event::EVENT_MOUSE_WHEEL_MOVED:
+ std::cout << "Event: Mouse Wheel (" << event.MouseWheel.Delta << ")" << std::endl;
+ break;
+ case Event::EVENT_MOUSE_BUTTON_PRESSED:
+ std::cout << "Event: Mouse Button Pressed " << MouseButtonName(event.MouseButton.Button) <<
+ " at (" << event.MouseButton.X << ", " << event.MouseButton.Y << ")" << std::endl;
+ break;
+ case Event::EVENT_MOUSE_BUTTON_RELEASED:
+ std::cout << "Event: Mouse Button Released " << MouseButtonName(event.MouseButton.Button) <<
+ " at (" << event.MouseButton.X << ", " << event.MouseButton.Y << ")" << std::endl;
+ break;
+ case Event::EVENT_MOUSE_MOVED:
+ std::cout << "Event: Mouse Moved (" << event.MouseMove.X
+ << ", " << event.MouseMove.Y << ")" << std::endl;
+ break;
+ case Event::EVENT_MOUSE_ENTERED:
+ std::cout << "Event: Mouse Entered Window" << std::endl;
+ break;
+ case Event::EVENT_MOUSE_LEFT:
+ std::cout << "Event: Mouse Left Window" << std::endl;
+ break;
+ case Event::EVENT_TEST:
+ std::cout << "Event: Test" << std::endl;
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+#endif
+
+OSWindow::OSWindow()
+ : mX(0),
+ mY(0),
+ mWidth(0),
+ mHeight(0)
+{
+}
+
+OSWindow::~OSWindow()
+{}
+
+int OSWindow::getX() const
+{
+ return mX;
+}
+
+int OSWindow::getY() const
+{
+ return mY;
+}
+
+int OSWindow::getWidth() const
+{
+ return mWidth;
+}
+
+int OSWindow::getHeight() const
+{
+ return mHeight;
+}
+
+bool OSWindow::takeScreenshot(uint8_t *pixelData)
+{
+ return false;
+}
+
+bool OSWindow::popEvent(Event *event)
+{
+ if (mEvents.size() > 0 && event)
+ {
+ *event = mEvents.front();
+ mEvents.pop_front();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void OSWindow::pushEvent(Event event)
+{
+ switch (event.Type)
+ {
+ case Event::EVENT_MOVED:
+ mX = event.Move.X;
+ mY = event.Move.Y;
+ break;
+ case Event::EVENT_RESIZED:
+ mWidth = event.Size.Width;
+ mHeight = event.Size.Height;
+ break;
+ default:
+ break;
+ }
+
+ mEvents.push_back(event);
+
+#if DEBUG_EVENTS
+ PrintEvent(event);
+#endif
+}
+
+bool OSWindow::didTestEventFire()
+{
+ Event topEvent;
+ while (popEvent(&topEvent))
+ {
+ if (topEvent.Type == Event::EVENT_TEST)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/stream-servers/testlibs/OSWindow.h b/stream-servers/testlibs/OSWindow.h
new file mode 100644
index 0000000..d266d9c
--- /dev/null
+++ b/stream-servers/testlibs/OSWindow.h
@@ -0,0 +1,74 @@
+//
+// Copyright (c) 2014 The ANGLE Project 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 SAMPLE_UTIL_WINDOW_H
+#define SAMPLE_UTIL_WINDOW_H
+
+#include <list>
+#include <stdint.h>
+#include <string>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "Event.h"
+
+class OSWindow
+{
+ public:
+ OSWindow();
+ virtual ~OSWindow();
+
+ virtual bool initialize(const std::string &name, size_t width, size_t height) = 0;
+ virtual void destroy() = 0;
+
+ int getX() const;
+ int getY() const;
+ int getWidth() const;
+ int getHeight() const;
+
+ // Takes a screenshot of the window, returning the result as a mWidth * mHeight * 4
+ // normalized unsigned byte BGRA array. Note that it will be used to test the window
+ // manager's behavior so it needs to take an actual screenshot of the screen and not
+ // just grab the pixels of the window. Returns if it was successful.
+ virtual bool takeScreenshot(uint8_t *pixelData);
+
+ virtual EGLNativeWindowType getNativeWindow() const = 0;
+ virtual EGLNativeDisplayType getNativeDisplay() const = 0;
+
+ virtual void* getFramebufferNativeWindow() const = 0;
+
+ virtual float getDevicePixelRatio() const {
+ return 1.0f;
+ }
+
+ virtual void messageLoop() = 0;
+
+ bool popEvent(Event *event);
+ virtual void pushEvent(Event event);
+
+ virtual void setMousePosition(int x, int y) = 0;
+ virtual bool setPosition(int x, int y) = 0;
+ virtual bool resize(int width, int height) = 0;
+ virtual void setVisible(bool isVisible) = 0;
+
+ virtual void signalTestEvent() = 0;
+
+ // Pops events look for the test event
+ bool didTestEventFire();
+
+ protected:
+ int mX;
+ int mY;
+ int mWidth;
+ int mHeight;
+
+ std::list<Event> mEvents;
+};
+
+OSWindow *CreateOSWindow();
+
+#endif // SAMPLE_UTIL_WINDOW_H
diff --git a/stream-servers/testlibs/Timer.h b/stream-servers/testlibs/Timer.h
new file mode 100644
index 0000000..db6991e
--- /dev/null
+++ b/stream-servers/testlibs/Timer.h
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2014 The ANGLE Project 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 SAMPLE_UTIL_TIMER_H
+#define SAMPLE_UTIL_TIMER_H
+
+class Timer
+{
+ public:
+ virtual ~Timer() {}
+ virtual void start() = 0;
+ virtual void stop() = 0;
+ virtual double getElapsedTime() const = 0;
+};
+
+Timer *CreateTimer();
+
+#endif // SAMPLE_UTIL_TIMER_H
diff --git a/stream-servers/testlibs/keyboard.h b/stream-servers/testlibs/keyboard.h
new file mode 100644
index 0000000..2718dd5
--- /dev/null
+++ b/stream-servers/testlibs/keyboard.h
@@ -0,0 +1,117 @@
+//
+// Copyright (c) 2014 The ANGLE Project 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 SAMPLE_UTIL_KEYBOARD_H
+#define SAMPLE_UTIL_KEYBOARD_H
+
+enum Key
+{
+ KEY_UNKNOWN,
+ KEY_A, // The A key
+ KEY_B, // The B key
+ KEY_C, // The C key
+ KEY_D, // The D key
+ KEY_E, // The E key
+ KEY_F, // The F key
+ KEY_G, // The G key
+ KEY_H, // The H key
+ KEY_I, // The I key
+ KEY_J, // The J key
+ KEY_K, // The K key
+ KEY_L, // The L key
+ KEY_M, // The M key
+ KEY_N, // The N key
+ KEY_O, // The O key
+ KEY_P, // The P key
+ KEY_Q, // The Q key
+ KEY_R, // The R key
+ KEY_S, // The S key
+ KEY_T, // The T key
+ KEY_U, // The U key
+ KEY_V, // The V key
+ KEY_W, // The W key
+ KEY_X, // The X key
+ KEY_Y, // The Y key
+ KEY_Z, // The Z key
+ KEY_NUM0, // The 0 key
+ KEY_NUM1, // The 1 key
+ KEY_NUM2, // The 2 key
+ KEY_NUM3, // The 3 key
+ KEY_NUM4, // The 4 key
+ KEY_NUM5, // The 5 key
+ KEY_NUM6, // The 6 key
+ KEY_NUM7, // The 7 key
+ KEY_NUM8, // The 8 key
+ KEY_NUM9, // The 9 key
+ KEY_ESCAPE, // The escape key
+ KEY_LCONTROL, // The left control key
+ KEY_LSHIFT, // The left shift key
+ KEY_LALT, // The left alt key
+ KEY_LSYSTEM, // The left OS specific key: Window (Windows and Linux), Apple (MacOS X), ...
+ KEY_RCONTROL, // The right control key
+ KEY_RSHIFT, // The right shift key
+ KEY_RALT, // The right alt key
+ KEY_RSYSTEM, // The right OS specific key: Window (Windows and Linux), Apple (MacOS X), ...
+ KEY_MENU, // The menu key
+ KEY_LBRACKET, // The [ key
+ KEY_RBRACKET, // The ] key
+ KEY_SEMICOLON, // The ; key
+ KEY_COMMA, // The , key
+ KEY_PERIOD, // The . key
+ KEY_QUOTE, // The ' key
+ KEY_SLASH, // The / key
+ KEY_BACKSLASH, // The \ key
+ KEY_TILDE, // The ~ key
+ KEY_EQUAL, // The = key
+ KEY_DASH, // The - key
+ KEY_SPACE, // The space key
+ KEY_RETURN, // The return key
+ KEY_BACK, // The backspace key
+ KEY_TAB, // The tabulation key
+ KEY_PAGEUP, // The page up key
+ KEY_PAGEDOWN, // The page down key
+ KEY_END, // The end key
+ KEY_HOME, // The home key
+ KEY_INSERT, // The insert key
+ KEY_DELETE, // The delete key
+ KEY_ADD, // +
+ KEY_SUBTRACT, // -
+ KEY_MULTIPLY, // *
+ KEY_DIVIDE, // /
+ KEY_LEFT, // Left arrow
+ KEY_RIGHT, // Right arrow
+ KEY_UP, // Up arrow
+ KEY_DOWN, // Down arrow
+ KEY_NUMPAD0, // The numpad 0 key
+ KEY_NUMPAD1, // The numpad 1 key
+ KEY_NUMPAD2, // The numpad 2 key
+ KEY_NUMPAD3, // The numpad 3 key
+ KEY_NUMPAD4, // The numpad 4 key
+ KEY_NUMPAD5, // The numpad 5 key
+ KEY_NUMPAD6, // The numpad 6 key
+ KEY_NUMPAD7, // The numpad 7 key
+ KEY_NUMPAD8, // The numpad 8 key
+ KEY_NUMPAD9, // The numpad 9 key
+ KEY_F1, // The F1 key
+ KEY_F2, // The F2 key
+ KEY_F3, // The F3 key
+ KEY_F4, // The F4 key
+ KEY_F5, // The F5 key
+ KEY_F6, // The F6 key
+ KEY_F7, // The F7 key
+ KEY_F8, // The F8 key
+ KEY_F9, // The F8 key
+ KEY_F10, // The F10 key
+ KEY_F11, // The F11 key
+ KEY_F12, // The F12 key
+ KEY_F13, // The F13 key
+ KEY_F14, // The F14 key
+ KEY_F15, // The F15 key
+ KEY_PAUSE, // The pause key
+ KEY_COUNT,
+};
+
+#endif // SAMPLE_UTIL_KEYBOARD_H
diff --git a/stream-servers/testlibs/mouse.h b/stream-servers/testlibs/mouse.h
new file mode 100644
index 0000000..d51b96d
--- /dev/null
+++ b/stream-servers/testlibs/mouse.h
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2014 The ANGLE Project 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 SAMPLE_UTIL_MOUSE_H
+#define SAMPLE_UTIL_MOUSE_H
+
+enum MouseButton
+{
+ MOUSEBUTTON_UNKNOWN,
+ MOUSEBUTTON_LEFT,
+ MOUSEBUTTON_RIGHT,
+ MOUSEBUTTON_MIDDLE,
+ MOUSEBUTTON_BUTTON4,
+ MOUSEBUTTON_BUTTON5,
+ MOUSEBUTTON_COUNT,
+};
+
+#endif // SAMPLE_UTIL_MOUSE_H
diff --git a/stream-servers/testlibs/osx/OSXWindow.h b/stream-servers/testlibs/osx/OSXWindow.h
new file mode 100644
index 0000000..0bc1cea
--- /dev/null
+++ b/stream-servers/testlibs/osx/OSXWindow.h
@@ -0,0 +1,65 @@
+//
+// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// OSXWindow.h: Definition of the implementation of OSWindow for OSX
+
+#ifndef UTIL_OSX_WINDOW_H_
+#define UTIL_OSX_WINDOW_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "OSWindow.h"
+
+class OSXWindow;
+
+@interface WindowDelegate : NSObject
+{
+ OSXWindow *mWindow;
+}
+- (id) initWithWindow: (OSXWindow*) window;
+@end
+
+@interface ContentView : NSView
+{
+ OSXWindow *mWindow;
+ NSTrackingArea *mTrackingArea;
+ int mCurrentModifier;
+}
+- (id) initWithWindow: (OSXWindow*) window;
+@end
+
+class OSXWindow : public OSWindow
+{
+ public:
+ OSXWindow();
+ ~OSXWindow();
+
+ bool initialize(const std::string &name, size_t width, size_t height) override;
+ void destroy() override;
+
+ EGLNativeWindowType getNativeWindow() const override;
+ EGLNativeDisplayType getNativeDisplay() const override;
+ void* getFramebufferNativeWindow() const override;
+ float getDevicePixelRatio() const override;
+
+ void messageLoop() override;
+
+ void setMousePosition(int x, int y) override;
+ bool setPosition(int x, int y) override;
+ bool resize(int width, int height) override;
+ void setVisible(bool isVisible) override;
+
+ void signalTestEvent() override;
+
+ NSWindow *getNSWindow() const;
+
+ private:
+ NSWindow *mWindow;
+ WindowDelegate *mDelegate;
+ ContentView *mView;
+};
+
+#endif // UTIL_OSX_WINDOW_H_
diff --git a/stream-servers/testlibs/osx/OSXWindow.mm b/stream-servers/testlibs/osx/OSXWindow.mm
new file mode 100644
index 0000000..5e29367
--- /dev/null
+++ b/stream-servers/testlibs/osx/OSXWindow.mm
@@ -0,0 +1,705 @@
+//
+// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// OSXWindow.mm: Implementation of OSWindow for OSX
+
+#include "osx/OSXWindow.h"
+
+#include <set>
+// Include Carbon to use the keycode names in Carbon's Event.h
+#include <Carbon/Carbon.h>
+
+// On OSX 10.12 a number of AppKit interfaces have been renamed for consistency, and the previous
+// symbols tagged as deprecated. However we can't simply use the new symbols as it would break
+// compilation on our automated testing that doesn't use OSX 10.12 yet. So we just ignore the
+// warnings.
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+// Some events such as "ShouldTerminate" are sent to the whole application so we keep a list of
+// all the windows in order to forward the event to each of them. However this and calling pushEvent
+// in ApplicationDelegate is inherently unsafe in a multithreaded environment.
+static std::set<OSXWindow*> gAllWindows;
+
+@interface Application : NSApplication
+@end
+
+@implementation Application
+ - (void) sendEvent: (NSEvent*) nsEvent
+ {
+ if ([nsEvent type] == NSApplicationDefined)
+ {
+ for (auto window : gAllWindows)
+ {
+ if ([window->getNSWindow() windowNumber] == [nsEvent windowNumber])
+ {
+ Event event;
+ event.Type = Event::EVENT_TEST;
+ window->pushEvent(event);
+ }
+ }
+ }
+ [super sendEvent: nsEvent];
+ }
+@end
+
+// The Delegate receiving application-wide events.
+@interface ApplicationDelegate : NSObject
+@end
+
+@implementation ApplicationDelegate
+ - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) sender
+ {
+ Event event;
+ event.Type = Event::EVENT_CLOSED;
+ for (auto window : gAllWindows)
+ {
+ window->pushEvent(event);
+ }
+ return NSTerminateCancel;
+ }
+@end
+static ApplicationDelegate *gApplicationDelegate = nil;
+
+static bool InitializeAppKit()
+{
+ if (NSApp != nil)
+ {
+ return true;
+ }
+
+ // Initialize the global variable "NSApp"
+ [Application sharedApplication];
+
+ // Make us appear in the dock
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+
+ // Register our global event handler
+ gApplicationDelegate = [[ApplicationDelegate alloc] init];
+ if (gApplicationDelegate == nil)
+ {
+ return false;
+ }
+ [NSApp setDelegate: static_cast<id>(gApplicationDelegate)];
+
+ // Set our status to "started" so we are not bouncing in the doc and can activate
+ [NSApp finishLaunching];
+ return true;
+}
+
+// NS's and CG's coordinate systems start at the bottom left, while OSWindow's coordinate
+// system starts at the top left. This function converts the Y coordinate accordingly.
+static float YCoordToFromCG(float y)
+{
+ float screenHeight = CGDisplayBounds(CGMainDisplayID()).size.height;
+ return screenHeight - y;
+}
+
+// Delegate for window-wide events, note that the protocol doesn't contain anything input related.
+@implementation WindowDelegate
+ - (id) initWithWindow: (OSXWindow*) window
+ {
+ self = [super init];
+ if (self != nil)
+ {
+ mWindow = window;
+ }
+ return self;
+ }
+
+ - (void) onOSXWindowDeleted
+ {
+ mWindow = nil;
+ }
+
+ - (BOOL) windowShouldClose: (id) sender
+ {
+ Event event;
+ event.Type = Event::EVENT_CLOSED;
+ mWindow->pushEvent(event);
+ return NO;
+ }
+
+ - (void) windowDidResize: (NSNotification*) notification
+ {
+ NSSize windowSize = [[mWindow->getNSWindow() contentView] frame].size;
+ Event event;
+ event.Type = Event::EVENT_RESIZED;
+ event.Size.Width = windowSize.width;
+ event.Size.Height = windowSize.height;
+ mWindow->pushEvent(event);
+ }
+
+ - (void) windowDidMove: (NSNotification*) notification
+ {
+ NSRect screenspace = [mWindow->getNSWindow() frame];
+ Event event;
+ event.Type = Event::EVENT_MOVED;
+ event.Move.X = screenspace.origin.x;
+ event.Move.Y = YCoordToFromCG(screenspace.origin.y + screenspace.size.height);
+ mWindow->pushEvent(event);
+ }
+
+ - (void) windowDidBecomeKey: (NSNotification*) notification
+ {
+ Event event;
+ event.Type = Event::EVENT_GAINED_FOCUS;
+ mWindow->pushEvent(event);
+ [self retain];
+ }
+
+ - (void) windowDidResignKey: (NSNotification*) notification
+ {
+ if (mWindow != nil)
+ {
+ Event event;
+ event.Type = Event::EVENT_LOST_FOCUS;
+ mWindow->pushEvent(event);
+ }
+ [self release];
+ }
+@end
+
+static Key NSCodeToKey(int keyCode)
+{
+ // Missing KEY_PAUSE
+ switch (keyCode)
+ {
+ case kVK_Shift: return KEY_LSHIFT;
+ case kVK_RightShift: return KEY_RSHIFT;
+ case kVK_Option: return KEY_LALT;
+ case kVK_RightOption: return KEY_RALT;
+ case kVK_Control: return KEY_LCONTROL;
+ case kVK_RightControl: return KEY_RCONTROL;
+ case kVK_Command: return KEY_LSYSTEM;
+ // Right System doesn't have a name, but shows up as 0x36.
+ case 0x36: return KEY_RSYSTEM;
+ case kVK_Function: return KEY_MENU;
+
+ case kVK_ANSI_Semicolon: return KEY_SEMICOLON;
+ case kVK_ANSI_Slash: return KEY_SLASH;
+ case kVK_ANSI_Equal: return KEY_EQUAL;
+ case kVK_ANSI_Minus: return KEY_DASH;
+ case kVK_ANSI_LeftBracket: return KEY_LBRACKET;
+ case kVK_ANSI_RightBracket: return KEY_RBRACKET;
+ case kVK_ANSI_Comma: return KEY_COMMA;
+ case kVK_ANSI_Period: return KEY_PERIOD;
+ case kVK_ANSI_Backslash: return KEY_BACKSLASH;
+ case kVK_ANSI_Grave: return KEY_TILDE;
+ case kVK_Escape: return KEY_ESCAPE;
+ case kVK_Space: return KEY_SPACE;
+ case kVK_Return: return KEY_RETURN;
+ case kVK_Delete: return KEY_BACK;
+ case kVK_Tab: return KEY_TAB;
+ case kVK_PageUp: return KEY_PAGEUP;
+ case kVK_PageDown: return KEY_PAGEDOWN;
+ case kVK_End: return KEY_END;
+ case kVK_Home: return KEY_HOME;
+ case kVK_Help: return KEY_INSERT;
+ case kVK_ForwardDelete: return KEY_DELETE;
+ case kVK_ANSI_KeypadPlus: return KEY_ADD;
+ case kVK_ANSI_KeypadMinus: return KEY_SUBTRACT;
+ case kVK_ANSI_KeypadMultiply: return KEY_MULTIPLY;
+ case kVK_ANSI_KeypadDivide: return KEY_DIVIDE;
+
+ case kVK_F1: return KEY_F1;
+ case kVK_F2: return KEY_F2;
+ case kVK_F3: return KEY_F3;
+ case kVK_F4: return KEY_F4;
+ case kVK_F5: return KEY_F5;
+ case kVK_F6: return KEY_F6;
+ case kVK_F7: return KEY_F7;
+ case kVK_F8: return KEY_F8;
+ case kVK_F9: return KEY_F9;
+ case kVK_F10: return KEY_F10;
+ case kVK_F11: return KEY_F11;
+ case kVK_F12: return KEY_F12;
+ case kVK_F13: return KEY_F13;
+ case kVK_F14: return KEY_F14;
+ case kVK_F15: return KEY_F15;
+
+ case kVK_LeftArrow: return KEY_LEFT;
+ case kVK_RightArrow: return KEY_RIGHT;
+ case kVK_DownArrow: return KEY_DOWN;
+ case kVK_UpArrow: return KEY_UP;
+
+ case kVK_ANSI_Keypad0: return KEY_NUMPAD0;
+ case kVK_ANSI_Keypad1: return KEY_NUMPAD1;
+ case kVK_ANSI_Keypad2: return KEY_NUMPAD2;
+ case kVK_ANSI_Keypad3: return KEY_NUMPAD3;
+ case kVK_ANSI_Keypad4: return KEY_NUMPAD4;
+ case kVK_ANSI_Keypad5: return KEY_NUMPAD5;
+ case kVK_ANSI_Keypad6: return KEY_NUMPAD6;
+ case kVK_ANSI_Keypad7: return KEY_NUMPAD7;
+ case kVK_ANSI_Keypad8: return KEY_NUMPAD8;
+ case kVK_ANSI_Keypad9: return KEY_NUMPAD9;
+
+ case kVK_ANSI_A: return KEY_A;
+ case kVK_ANSI_B: return KEY_B;
+ case kVK_ANSI_C: return KEY_C;
+ case kVK_ANSI_D: return KEY_D;
+ case kVK_ANSI_E: return KEY_E;
+ case kVK_ANSI_F: return KEY_F;
+ case kVK_ANSI_G: return KEY_G;
+ case kVK_ANSI_H: return KEY_H;
+ case kVK_ANSI_I: return KEY_I;
+ case kVK_ANSI_J: return KEY_J;
+ case kVK_ANSI_K: return KEY_K;
+ case kVK_ANSI_L: return KEY_L;
+ case kVK_ANSI_M: return KEY_M;
+ case kVK_ANSI_N: return KEY_N;
+ case kVK_ANSI_O: return KEY_O;
+ case kVK_ANSI_P: return KEY_P;
+ case kVK_ANSI_Q: return KEY_Q;
+ case kVK_ANSI_R: return KEY_R;
+ case kVK_ANSI_S: return KEY_S;
+ case kVK_ANSI_T: return KEY_T;
+ case kVK_ANSI_U: return KEY_U;
+ case kVK_ANSI_V: return KEY_V;
+ case kVK_ANSI_W: return KEY_W;
+ case kVK_ANSI_X: return KEY_X;
+ case kVK_ANSI_Y: return KEY_Y;
+ case kVK_ANSI_Z: return KEY_Z;
+
+ case kVK_ANSI_1: return KEY_NUM1;
+ case kVK_ANSI_2: return KEY_NUM2;
+ case kVK_ANSI_3: return KEY_NUM3;
+ case kVK_ANSI_4: return KEY_NUM4;
+ case kVK_ANSI_5: return KEY_NUM5;
+ case kVK_ANSI_6: return KEY_NUM6;
+ case kVK_ANSI_7: return KEY_NUM7;
+ case kVK_ANSI_8: return KEY_NUM8;
+ case kVK_ANSI_9: return KEY_NUM9;
+ case kVK_ANSI_0: return KEY_NUM0;
+ }
+
+ return Key(0);
+}
+
+static void AddNSKeyStateToEvent(Event *event, int state)
+{
+ event->Key.Shift = state & NSShiftKeyMask;
+ event->Key.Control = state & NSControlKeyMask;
+ event->Key.Alt = state & NSAlternateKeyMask;
+ event->Key.System = state & NSCommandKeyMask;
+}
+
+static MouseButton TranslateMouseButton(int button)
+{
+ switch (button)
+ {
+ case 2:
+ return MOUSEBUTTON_MIDDLE;
+ case 3:
+ return MOUSEBUTTON_BUTTON4;
+ case 4:
+ return MOUSEBUTTON_BUTTON5;
+ default:
+ return MOUSEBUTTON_UNKNOWN;
+ }
+}
+
+// Delegate for NSView events, mostly the input events
+@implementation ContentView
+ - (id) initWithWindow: (OSXWindow*) window
+ {
+ self = [super init];
+ if (self != nil)
+ {
+ mWindow = window;
+ mTrackingArea = nil;
+ mCurrentModifier = 0;
+ [self updateTrackingAreas];
+ }
+ return self;
+ }
+
+ - (void) dealloc
+ {
+ [mTrackingArea release];
+ [super dealloc];
+ }
+
+ - (void) updateTrackingAreas
+ {
+ if (mTrackingArea != nil)
+ {
+ [self removeTrackingArea: mTrackingArea];
+ [mTrackingArea release];
+ mTrackingArea = nil;
+ }
+
+ NSRect bounds = [self bounds];
+ NSTrackingAreaOptions flags = NSTrackingMouseEnteredAndExited |
+ NSTrackingActiveInKeyWindow |
+ NSTrackingCursorUpdate |
+ NSTrackingInVisibleRect |
+ NSTrackingAssumeInside;
+ mTrackingArea = [[NSTrackingArea alloc] initWithRect: bounds
+ options: flags
+ owner: self
+ userInfo: nil];
+
+ [self addTrackingArea: mTrackingArea];
+ [super updateTrackingAreas];
+ }
+
+ // Helps with performance
+ - (BOOL) isOpaque
+ {
+ return YES;
+ }
+
+ - (BOOL) canBecomeKeyView
+ {
+ return YES;
+ }
+
+ - (BOOL) acceptsFirstResponder
+ {
+ return YES;
+ }
+
+ // Handle mouse events from the NSResponder protocol
+ - (float) translateMouseY: (float) y
+ {
+ return [self frame].size.height - y;
+ }
+
+ - (void) addButtonEvent: (NSEvent*) nsEvent type:(Event::EventType) eventType button:(MouseButton) button
+ {
+ Event event;
+ event.Type = eventType;
+ event.MouseButton.Button = button;
+ event.MouseButton.X = [nsEvent locationInWindow].x;
+ event.MouseButton.Y = [self translateMouseY: [nsEvent locationInWindow].y];
+ mWindow->pushEvent(event);
+ }
+
+ - (void) mouseDown: (NSEvent*) event
+ {
+ [self addButtonEvent: event
+ type: Event::EVENT_MOUSE_BUTTON_PRESSED
+ button: MOUSEBUTTON_LEFT];
+ }
+
+ - (void) mouseDragged: (NSEvent*) event
+ {
+ [self mouseMoved: event];
+ }
+
+ - (void) mouseUp: (NSEvent*) event
+ {
+ [self addButtonEvent: event
+ type: Event::EVENT_MOUSE_BUTTON_RELEASED
+ button: MOUSEBUTTON_LEFT];
+ }
+
+ - (void) mouseMoved: (NSEvent*) nsEvent
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_MOVED;
+ event.MouseMove.X = [nsEvent locationInWindow].x;
+ event.MouseMove.Y = [self translateMouseY: [nsEvent locationInWindow].y];
+ mWindow->pushEvent(event);
+ }
+
+ - (void) mouseEntered: (NSEvent*) nsEvent
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_ENTERED;
+ mWindow->pushEvent(event);
+ }
+
+ - (void) mouseExited: (NSEvent*) nsEvent
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_LEFT;
+ mWindow->pushEvent(event);
+ }
+
+ - (void)rightMouseDown:(NSEvent *)event
+ {
+ [self addButtonEvent: event
+ type: Event::EVENT_MOUSE_BUTTON_PRESSED
+ button: MOUSEBUTTON_RIGHT];
+ }
+
+ - (void) rightMouseDragged: (NSEvent*) event
+ {
+ [self mouseMoved: event];
+ }
+
+ - (void) rightMouseUp: (NSEvent*)event
+ {
+ [self addButtonEvent: event
+ type: Event::EVENT_MOUSE_BUTTON_RELEASED
+ button: MOUSEBUTTON_RIGHT];
+ }
+
+ - (void) otherMouseDown: (NSEvent*) event
+ {
+ [self addButtonEvent: event
+ type: Event::EVENT_MOUSE_BUTTON_PRESSED
+ button: TranslateMouseButton([event buttonNumber])];
+ }
+
+ - (void) otherMouseDragged: (NSEvent*) event
+ {
+ [self mouseMoved: event];
+ }
+
+ - (void) otherMouseUp: (NSEvent*) event
+ {
+ [self addButtonEvent: event
+ type: Event::EVENT_MOUSE_BUTTON_RELEASED
+ button: TranslateMouseButton([event buttonNumber])];
+ }
+
+ - (void) scrollWheel: (NSEvent*) nsEvent
+ {
+ if (static_cast<int>([nsEvent deltaY]) == 0)
+ {
+ return;
+ }
+
+ Event event;
+ event.Type = Event::EVENT_MOUSE_WHEEL_MOVED;
+ event.MouseWheel.Delta = [nsEvent deltaY];
+ mWindow->pushEvent(event);
+ }
+
+ // Handle key events from the NSResponder protocol
+ - (void) keyDown: (NSEvent*) nsEvent
+ {
+ // TODO(cwallez) also send text events
+ Event event;
+ event.Type = Event::EVENT_KEY_PRESSED;
+ event.Key.Code = NSCodeToKey([nsEvent keyCode]);
+ AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]);
+ mWindow->pushEvent(event);
+ }
+
+ - (void) keyUp: (NSEvent*) nsEvent
+ {
+ Event event;
+ event.Type = Event::EVENT_KEY_RELEASED;
+ event.Key.Code = NSCodeToKey([nsEvent keyCode]);
+ AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]);
+ mWindow->pushEvent(event);
+ }
+
+ // Modifier keys do not trigger keyUp/Down events but only flagsChanged events.
+ - (void) flagsChanged: (NSEvent*) nsEvent
+ {
+ Event event;
+
+ // Guess if the key has been pressed or released with the change of modifiers
+ // It currently doesn't work when modifiers are unchanged, such as when pressing
+ // both shift keys. GLFW has a solution for this but it requires tracking the
+ // state of the keys. Implementing this is still TODO(cwallez)
+ int modifier = [nsEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
+ if (modifier < mCurrentModifier)
+ {
+ event.Type = Event::EVENT_KEY_RELEASED;
+ }
+ else
+ {
+ event.Type = Event::EVENT_KEY_PRESSED;
+ }
+ mCurrentModifier = modifier;
+
+ event.Key.Code = NSCodeToKey([nsEvent keyCode]);
+ AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]);
+ mWindow->pushEvent(event);
+ }
+@end
+
+OSXWindow::OSXWindow()
+ : mWindow(nil),
+ mDelegate(nil),
+ mView(nil)
+{
+}
+
+OSXWindow::~OSXWindow()
+{
+ destroy();
+}
+
+bool OSXWindow::initialize(const std::string &name, size_t width, size_t height)
+{
+ if (!InitializeAppKit())
+ {
+ return false;
+ }
+
+ unsigned int styleMask = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask |
+ NSMiniaturizableWindowMask;
+ mWindow = [[NSWindow alloc] initWithContentRect: NSMakeRect(0, 0, width, height)
+ styleMask: styleMask
+ backing: NSBackingStoreBuffered
+ defer: NO];
+
+ if (mWindow == nil)
+ {
+ return false;
+ }
+
+ mDelegate = [[WindowDelegate alloc] initWithWindow: this];
+ if (mDelegate == nil)
+ {
+ return false;
+ }
+ [mWindow setDelegate: static_cast<id>(mDelegate)];
+
+ mView = [[ContentView alloc] initWithWindow: this];
+ if (mView == nil)
+ {
+ return false;
+ }
+ [mView setWantsLayer:YES];
+
+ [mWindow setContentView: mView];
+ [mWindow setTitle: [NSString stringWithUTF8String: name.c_str()]];
+ [mWindow setAcceptsMouseMovedEvents: YES];
+ [mWindow center];
+
+ [NSApp activateIgnoringOtherApps: YES];
+
+ mX = 0;
+ mY = 0;
+ mWidth = width;
+ mHeight = height;
+
+ gAllWindows.insert(this);
+ return true;
+}
+
+void OSXWindow::destroy()
+{
+ gAllWindows.erase(this);
+
+ [mView release];
+ mView = nil;
+ [mDelegate onOSXWindowDeleted];
+ [mDelegate release];
+ mDelegate = nil;
+ [mWindow release];
+ mWindow = nil;
+}
+
+EGLNativeWindowType OSXWindow::getNativeWindow() const
+{
+ return [mView layer];
+}
+
+EGLNativeDisplayType OSXWindow::getNativeDisplay() const
+{
+ // TODO(cwallez): implement it once we have defined what EGLNativeDisplayType is
+ return static_cast<EGLNativeDisplayType>(0);
+}
+
+void* OSXWindow::getFramebufferNativeWindow() const
+{
+ return static_cast<void*>(mWindow);
+}
+
+float OSXWindow::getDevicePixelRatio() const
+{
+ return [[NSScreen mainScreen] backingScaleFactor];
+}
+
+void OSXWindow::messageLoop()
+{
+ @autoreleasepool
+ {
+ while (true)
+ {
+ NSEvent* event = [NSApp nextEventMatchingMask: NSAnyEventMask
+ untilDate: [NSDate distantPast]
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ if (event == nil)
+ {
+ break;
+ }
+
+ if ([event type] == NSAppKitDefined)
+ {
+ continue;
+ }
+ [NSApp sendEvent: event];
+ }
+ }
+}
+
+void OSXWindow::setMousePosition(int x, int y)
+{
+ y = [mWindow frame].size.height - y -1;
+ NSPoint screenspace;
+
+ #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
+ screenspace = [mWindow convertBaseToScreen: NSMakePoint(x, y)];
+ #else
+ screenspace = [mWindow convertRectToScreen: NSMakeRect(x, y, 0, 0)].origin;
+ #endif
+ CGWarpMouseCursorPosition(CGPointMake(screenspace.x, YCoordToFromCG(screenspace.y)));
+}
+
+bool OSXWindow::setPosition(int x, int y)
+{
+ // Given CG and NS's coordinate system, the "Y" position of a window is the Y coordinate
+ // of the bottom of the window.
+ int newBottom = [mWindow frame].size.height + y;
+ NSRect emptyRect = NSMakeRect(x, YCoordToFromCG(newBottom), 0, 0);
+ [mWindow setFrameOrigin: [mWindow frameRectForContentRect: emptyRect].origin];
+ return true;
+}
+
+bool OSXWindow::resize(int width, int height)
+{
+ [mWindow setContentSize: NSMakeSize(width, height)];
+ return true;
+}
+
+void OSXWindow::setVisible(bool isVisible)
+{
+ if (isVisible)
+ {
+ [mWindow makeKeyAndOrderFront: nil];
+ }
+ else
+ {
+ [mWindow orderOut: nil];
+ }
+}
+
+void OSXWindow::signalTestEvent()
+{
+ @autoreleasepool
+ {
+ NSEvent *event = [NSEvent otherEventWithType: NSApplicationDefined
+ location: NSMakePoint(0, 0)
+ modifierFlags: 0
+ timestamp: 0.0
+ windowNumber: [mWindow windowNumber]
+ context: nil
+ subtype: 0
+ data1: 0
+ data2: 0];
+ [NSApp postEvent: event atStart: YES];
+ }
+}
+
+NSWindow* OSXWindow::getNSWindow() const
+{
+ return mWindow;
+}
+
+OSWindow *CreateOSWindow()
+{
+ return new OSXWindow;
+}
diff --git a/stream-servers/testlibs/windows/WindowsTimer.cpp b/stream-servers/testlibs/windows/WindowsTimer.cpp
new file mode 100644
index 0000000..c6a9e9d
--- /dev/null
+++ b/stream-servers/testlibs/windows/WindowsTimer.cpp
@@ -0,0 +1,57 @@
+//
+// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// WindowsTimer.cpp: Implementation of a high precision timer class on Windows
+
+#include "windows/WindowsTimer.h"
+
+WindowsTimer::WindowsTimer() : mRunning(false), mStartTime(0), mStopTime(0)
+{
+}
+
+void WindowsTimer::start()
+{
+ LARGE_INTEGER frequency;
+ QueryPerformanceFrequency(&frequency);
+ mFrequency = frequency.QuadPart;
+
+ LARGE_INTEGER curTime;
+ QueryPerformanceCounter(&curTime);
+ mStartTime = curTime.QuadPart;
+
+ mRunning = true;
+}
+
+void WindowsTimer::stop()
+{
+ LARGE_INTEGER curTime;
+ QueryPerformanceCounter(&curTime);
+ mStopTime = curTime.QuadPart;
+
+ mRunning = false;
+}
+
+double WindowsTimer::getElapsedTime() const
+{
+ LONGLONG endTime;
+ if (mRunning)
+ {
+ LARGE_INTEGER curTime;
+ QueryPerformanceCounter(&curTime);
+ endTime = curTime.QuadPart;
+ }
+ else
+ {
+ endTime = mStopTime;
+ }
+
+ return static_cast<double>(endTime - mStartTime) / mFrequency;
+}
+
+Timer *CreateTimer()
+{
+ return new WindowsTimer();
+}
diff --git a/stream-servers/testlibs/windows/WindowsTimer.h b/stream-servers/testlibs/windows/WindowsTimer.h
new file mode 100644
index 0000000..a194300
--- /dev/null
+++ b/stream-servers/testlibs/windows/WindowsTimer.h
@@ -0,0 +1,33 @@
+//
+// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// WindowsTimer.h: Definition of a high precision timer class on Windows
+
+#ifndef UTIL_WINDOWS_TIMER_H
+#define UTIL_WINDOWS_TIMER_H
+
+#include <windows.h>
+
+#include "Timer.h"
+
+class WindowsTimer : public Timer
+{
+ public:
+ WindowsTimer();
+
+ void start() override;
+ void stop() override;
+ double getElapsedTime() const override;
+
+ private:
+ bool mRunning;
+ LONGLONG mStartTime;
+ LONGLONG mStopTime;
+
+ LONGLONG mFrequency;
+};
+
+#endif // UTIL_WINDOWS_TIMER_H
diff --git a/stream-servers/testlibs/windows/Windows_system_utils.cpp b/stream-servers/testlibs/windows/Windows_system_utils.cpp
new file mode 100644
index 0000000..36b4f66
--- /dev/null
+++ b/stream-servers/testlibs/windows/Windows_system_utils.cpp
@@ -0,0 +1,39 @@
+//
+// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// Windows_system_utils.cpp: Implementation of OS-specific functions for Windows
+
+#include "system_utils.h"
+
+#include <stdarg.h>
+#include <windows.h>
+#include <array>
+#include <vector>
+
+namespace angle
+{
+
+void Sleep(unsigned int milliseconds)
+{
+ ::Sleep(static_cast<DWORD>(milliseconds));
+}
+
+void WriteDebugMessage(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ int size = vsnprintf(nullptr, 0, format, args);
+ va_end(args);
+
+ std::vector<char> buffer(size + 2);
+ va_start(args, format);
+ vsnprintf(buffer.data(), size + 1, format, args);
+ va_end(args);
+
+ OutputDebugStringA(buffer.data());
+}
+
+} // namespace angle
diff --git a/stream-servers/testlibs/windows/win32/Win32Window.cpp b/stream-servers/testlibs/windows/win32/Win32Window.cpp
new file mode 100644
index 0000000..457bb9e
--- /dev/null
+++ b/stream-servers/testlibs/windows/win32/Win32Window.cpp
@@ -0,0 +1,836 @@
+//
+// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// Win32Window.cpp: Implementation of OSWindow for Win32 (Windows)
+
+#include "windows/win32/Win32Window.h"
+
+#include <sstream>
+
+Key VirtualKeyCodeToKey(WPARAM key, LPARAM flags)
+{
+ switch (key)
+ {
+ // Check the scancode to distinguish between left and right shift
+ case VK_SHIFT:
+ {
+ static unsigned int lShift = MapVirtualKey(VK_LSHIFT, MAPVK_VK_TO_VSC);
+ unsigned int scancode = static_cast<unsigned int>((flags & (0xFF << 16)) >> 16);
+ return scancode == lShift ? KEY_LSHIFT : KEY_RSHIFT;
+ }
+
+ // Check the "extended" flag to distinguish between left and right alt
+ case VK_MENU:
+ return (HIWORD(flags) & KF_EXTENDED) ? KEY_RALT : KEY_LALT;
+
+ // Check the "extended" flag to distinguish between left and right control
+ case VK_CONTROL:
+ return (HIWORD(flags) & KF_EXTENDED) ? KEY_RCONTROL : KEY_LCONTROL;
+
+ // Other keys are reported properly
+ case VK_LWIN:
+ return KEY_LSYSTEM;
+ case VK_RWIN:
+ return KEY_RSYSTEM;
+ case VK_APPS:
+ return KEY_MENU;
+ case VK_OEM_1:
+ return KEY_SEMICOLON;
+ case VK_OEM_2:
+ return KEY_SLASH;
+ case VK_OEM_PLUS:
+ return KEY_EQUAL;
+ case VK_OEM_MINUS:
+ return KEY_DASH;
+ case VK_OEM_4:
+ return KEY_LBRACKET;
+ case VK_OEM_6:
+ return KEY_RBRACKET;
+ case VK_OEM_COMMA:
+ return KEY_COMMA;
+ case VK_OEM_PERIOD:
+ return KEY_PERIOD;
+ case VK_OEM_7:
+ return KEY_QUOTE;
+ case VK_OEM_5:
+ return KEY_BACKSLASH;
+ case VK_OEM_3:
+ return KEY_TILDE;
+ case VK_ESCAPE:
+ return KEY_ESCAPE;
+ case VK_SPACE:
+ return KEY_SPACE;
+ case VK_RETURN:
+ return KEY_RETURN;
+ case VK_BACK:
+ return KEY_BACK;
+ case VK_TAB:
+ return KEY_TAB;
+ case VK_PRIOR:
+ return KEY_PAGEUP;
+ case VK_NEXT:
+ return KEY_PAGEDOWN;
+ case VK_END:
+ return KEY_END;
+ case VK_HOME:
+ return KEY_HOME;
+ case VK_INSERT:
+ return KEY_INSERT;
+ case VK_DELETE:
+ return KEY_DELETE;
+ case VK_ADD:
+ return KEY_ADD;
+ case VK_SUBTRACT:
+ return KEY_SUBTRACT;
+ case VK_MULTIPLY:
+ return KEY_MULTIPLY;
+ case VK_DIVIDE:
+ return KEY_DIVIDE;
+ case VK_PAUSE:
+ return KEY_PAUSE;
+ case VK_F1:
+ return KEY_F1;
+ case VK_F2:
+ return KEY_F2;
+ case VK_F3:
+ return KEY_F3;
+ case VK_F4:
+ return KEY_F4;
+ case VK_F5:
+ return KEY_F5;
+ case VK_F6:
+ return KEY_F6;
+ case VK_F7:
+ return KEY_F7;
+ case VK_F8:
+ return KEY_F8;
+ case VK_F9:
+ return KEY_F9;
+ case VK_F10:
+ return KEY_F10;
+ case VK_F11:
+ return KEY_F11;
+ case VK_F12:
+ return KEY_F12;
+ case VK_F13:
+ return KEY_F13;
+ case VK_F14:
+ return KEY_F14;
+ case VK_F15:
+ return KEY_F15;
+ case VK_LEFT:
+ return KEY_LEFT;
+ case VK_RIGHT:
+ return KEY_RIGHT;
+ case VK_UP:
+ return KEY_UP;
+ case VK_DOWN:
+ return KEY_DOWN;
+ case VK_NUMPAD0:
+ return KEY_NUMPAD0;
+ case VK_NUMPAD1:
+ return KEY_NUMPAD1;
+ case VK_NUMPAD2:
+ return KEY_NUMPAD2;
+ case VK_NUMPAD3:
+ return KEY_NUMPAD3;
+ case VK_NUMPAD4:
+ return KEY_NUMPAD4;
+ case VK_NUMPAD5:
+ return KEY_NUMPAD5;
+ case VK_NUMPAD6:
+ return KEY_NUMPAD6;
+ case VK_NUMPAD7:
+ return KEY_NUMPAD7;
+ case VK_NUMPAD8:
+ return KEY_NUMPAD8;
+ case VK_NUMPAD9:
+ return KEY_NUMPAD9;
+ case 'A':
+ return KEY_A;
+ case 'Z':
+ return KEY_Z;
+ case 'E':
+ return KEY_E;
+ case 'R':
+ return KEY_R;
+ case 'T':
+ return KEY_T;
+ case 'Y':
+ return KEY_Y;
+ case 'U':
+ return KEY_U;
+ case 'I':
+ return KEY_I;
+ case 'O':
+ return KEY_O;
+ case 'P':
+ return KEY_P;
+ case 'Q':
+ return KEY_Q;
+ case 'S':
+ return KEY_S;
+ case 'D':
+ return KEY_D;
+ case 'F':
+ return KEY_F;
+ case 'G':
+ return KEY_G;
+ case 'H':
+ return KEY_H;
+ case 'J':
+ return KEY_J;
+ case 'K':
+ return KEY_K;
+ case 'L':
+ return KEY_L;
+ case 'M':
+ return KEY_M;
+ case 'W':
+ return KEY_W;
+ case 'X':
+ return KEY_X;
+ case 'C':
+ return KEY_C;
+ case 'V':
+ return KEY_V;
+ case 'B':
+ return KEY_B;
+ case 'N':
+ return KEY_N;
+ case '0':
+ return KEY_NUM0;
+ case '1':
+ return KEY_NUM1;
+ case '2':
+ return KEY_NUM2;
+ case '3':
+ return KEY_NUM3;
+ case '4':
+ return KEY_NUM4;
+ case '5':
+ return KEY_NUM5;
+ case '6':
+ return KEY_NUM6;
+ case '7':
+ return KEY_NUM7;
+ case '8':
+ return KEY_NUM8;
+ case '9':
+ return KEY_NUM9;
+ }
+
+ return Key(0);
+}
+
+LRESULT CALLBACK Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_NCCREATE:
+ {
+ LPCREATESTRUCT pCreateStruct = reinterpret_cast<LPCREATESTRUCT>(lParam);
+ SetWindowLongPtr(hWnd, GWLP_USERDATA,
+ reinterpret_cast<LONG_PTR>(pCreateStruct->lpCreateParams));
+ return DefWindowProcA(hWnd, message, wParam, lParam);
+ }
+ }
+
+ Win32Window *window = reinterpret_cast<Win32Window *>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
+ if (window)
+ {
+ switch (message)
+ {
+ case WM_DESTROY:
+ case WM_CLOSE:
+ {
+ Event event;
+ event.Type = Event::EVENT_CLOSED;
+ window->pushEvent(event);
+ break;
+ }
+
+ case WM_MOVE:
+ {
+ RECT winRect;
+ GetClientRect(hWnd, &winRect);
+
+ POINT topLeft;
+ topLeft.x = winRect.left;
+ topLeft.y = winRect.top;
+ ClientToScreen(hWnd, &topLeft);
+
+ Event event;
+ event.Type = Event::EVENT_MOVED;
+ event.Move.X = topLeft.x;
+ event.Move.Y = topLeft.y;
+ window->pushEvent(event);
+
+ break;
+ }
+
+ case WM_SIZE:
+ {
+ RECT winRect;
+ GetClientRect(hWnd, &winRect);
+
+ POINT topLeft;
+ topLeft.x = winRect.left;
+ topLeft.y = winRect.top;
+ ClientToScreen(hWnd, &topLeft);
+
+ POINT botRight;
+ botRight.x = winRect.right;
+ botRight.y = winRect.bottom;
+ ClientToScreen(hWnd, &botRight);
+
+ Event event;
+ event.Type = Event::EVENT_RESIZED;
+ event.Size.Width = botRight.x - topLeft.x;
+ event.Size.Height = botRight.y - topLeft.y;
+ window->pushEvent(event);
+
+ break;
+ }
+
+ case WM_SETFOCUS:
+ {
+ Event event;
+ event.Type = Event::EVENT_GAINED_FOCUS;
+ window->pushEvent(event);
+ break;
+ }
+
+ case WM_KILLFOCUS:
+ {
+ Event event;
+ event.Type = Event::EVENT_LOST_FOCUS;
+ window->pushEvent(event);
+ break;
+ }
+
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ {
+ bool down = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN);
+
+ Event event;
+ event.Type = down ? Event::EVENT_KEY_PRESSED : Event::EVENT_KEY_RELEASED;
+ event.Key.Alt = HIWORD(GetAsyncKeyState(VK_MENU)) != 0;
+ event.Key.Control = HIWORD(GetAsyncKeyState(VK_CONTROL)) != 0;
+ event.Key.Shift = HIWORD(GetAsyncKeyState(VK_SHIFT)) != 0;
+ event.Key.System =
+ HIWORD(GetAsyncKeyState(VK_LWIN)) || HIWORD(GetAsyncKeyState(VK_RWIN));
+ event.Key.Code = VirtualKeyCodeToKey(wParam, lParam);
+ window->pushEvent(event);
+
+ break;
+ }
+
+ case WM_MOUSEWHEEL:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_WHEEL_MOVED;
+ event.MouseWheel.Delta = static_cast<short>(HIWORD(wParam)) / 120;
+ window->pushEvent(event);
+ break;
+ }
+
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONDBLCLK:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_BUTTON_PRESSED;
+ event.MouseButton.Button = MOUSEBUTTON_LEFT;
+ event.MouseButton.X = static_cast<short>(LOWORD(lParam));
+ event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
+ window->pushEvent(event);
+ break;
+ }
+
+ case WM_LBUTTONUP:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
+ event.MouseButton.Button = MOUSEBUTTON_LEFT;
+ event.MouseButton.X = static_cast<short>(LOWORD(lParam));
+ event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
+ window->pushEvent(event);
+ break;
+ }
+
+ case WM_RBUTTONDOWN:
+ case WM_RBUTTONDBLCLK:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_BUTTON_PRESSED;
+ event.MouseButton.Button = MOUSEBUTTON_RIGHT;
+ event.MouseButton.X = static_cast<short>(LOWORD(lParam));
+ event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
+ window->pushEvent(event);
+ break;
+ }
+
+ // Mouse right button up event
+ case WM_RBUTTONUP:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
+ event.MouseButton.Button = MOUSEBUTTON_RIGHT;
+ event.MouseButton.X = static_cast<short>(LOWORD(lParam));
+ event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
+ window->pushEvent(event);
+ break;
+ }
+
+ // Mouse wheel button down event
+ case WM_MBUTTONDOWN:
+ case WM_MBUTTONDBLCLK:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_BUTTON_PRESSED;
+ event.MouseButton.Button = MOUSEBUTTON_MIDDLE;
+ event.MouseButton.X = static_cast<short>(LOWORD(lParam));
+ event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
+ window->pushEvent(event);
+ break;
+ }
+
+ // Mouse wheel button up event
+ case WM_MBUTTONUP:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
+ event.MouseButton.Button = MOUSEBUTTON_MIDDLE;
+ event.MouseButton.X = static_cast<short>(LOWORD(lParam));
+ event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
+ window->pushEvent(event);
+ break;
+ }
+
+ // Mouse X button down event
+ case WM_XBUTTONDOWN:
+ case WM_XBUTTONDBLCLK:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_BUTTON_PRESSED;
+ event.MouseButton.Button =
+ (HIWORD(wParam) == XBUTTON1) ? MOUSEBUTTON_BUTTON4 : MOUSEBUTTON_BUTTON5;
+ event.MouseButton.X = static_cast<short>(LOWORD(lParam));
+ event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
+ window->pushEvent(event);
+ break;
+ }
+
+ // Mouse X button up event
+ case WM_XBUTTONUP:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
+ event.MouseButton.Button =
+ (HIWORD(wParam) == XBUTTON1) ? MOUSEBUTTON_BUTTON4 : MOUSEBUTTON_BUTTON5;
+ event.MouseButton.X = static_cast<short>(LOWORD(lParam));
+ event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
+ window->pushEvent(event);
+ break;
+ }
+
+ case WM_MOUSEMOVE:
+ {
+ if (!window->mIsMouseInWindow)
+ {
+ window->mIsMouseInWindow = true;
+ Event event;
+ event.Type = Event::EVENT_MOUSE_ENTERED;
+ window->pushEvent(event);
+ }
+
+ int mouseX = static_cast<short>(LOWORD(lParam));
+ int mouseY = static_cast<short>(HIWORD(lParam));
+
+ Event event;
+ event.Type = Event::EVENT_MOUSE_MOVED;
+ event.MouseMove.X = mouseX;
+ event.MouseMove.Y = mouseY;
+ window->pushEvent(event);
+ break;
+ }
+
+ case WM_MOUSELEAVE:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_LEFT;
+ window->pushEvent(event);
+ window->mIsMouseInWindow = false;
+ break;
+ }
+
+ case WM_USER:
+ {
+ Event testEvent;
+ testEvent.Type = Event::EVENT_TEST;
+ window->pushEvent(testEvent);
+ break;
+ }
+ }
+ }
+ return DefWindowProcA(hWnd, message, wParam, lParam);
+}
+
+Win32Window::Win32Window()
+ : mIsVisible(false),
+ mSetVisibleTimer(CreateTimer()),
+ mIsMouseInWindow(false),
+ mNativeWindow(0),
+ mParentWindow(0),
+ mNativeDisplay(0)
+{
+}
+
+Win32Window::~Win32Window()
+{
+ destroy();
+ delete mSetVisibleTimer;
+}
+
+bool Win32Window::initialize(const std::string &name, size_t width, size_t height)
+{
+ destroy();
+
+ // Use a new window class name for ever window to ensure that a new window can be created
+ // even if the last one was not properly destroyed
+ static size_t windowIdx = 0;
+ std::ostringstream nameStream;
+ nameStream << name << "_" << windowIdx++;
+
+ mParentClassName = nameStream.str();
+ mChildClassName = mParentClassName + "_Child";
+
+ // Work around compile error from not defining "UNICODE" while Chromium does
+ const LPSTR idcArrow = MAKEINTRESOURCEA(32512);
+
+ WNDCLASSEXA parentWindowClass = {0};
+ parentWindowClass.cbSize = sizeof(WNDCLASSEXA);
+ parentWindowClass.style = 0;
+ parentWindowClass.lpfnWndProc = WndProc;
+ parentWindowClass.cbClsExtra = 0;
+ parentWindowClass.cbWndExtra = 0;
+ parentWindowClass.hInstance = GetModuleHandle(nullptr);
+ parentWindowClass.hIcon = nullptr;
+ parentWindowClass.hCursor = LoadCursorA(nullptr, idcArrow);
+ parentWindowClass.hbrBackground = 0;
+ parentWindowClass.lpszMenuName = nullptr;
+ parentWindowClass.lpszClassName = mParentClassName.c_str();
+ if (!RegisterClassExA(&parentWindowClass))
+ {
+ return false;
+ }
+
+ WNDCLASSEXA childWindowClass = {0};
+ childWindowClass.cbSize = sizeof(WNDCLASSEXA);
+ childWindowClass.style = CS_OWNDC;
+ childWindowClass.lpfnWndProc = WndProc;
+ childWindowClass.cbClsExtra = 0;
+ childWindowClass.cbWndExtra = 0;
+ childWindowClass.hInstance = GetModuleHandle(nullptr);
+ childWindowClass.hIcon = nullptr;
+ childWindowClass.hCursor = LoadCursorA(nullptr, idcArrow);
+ childWindowClass.hbrBackground = 0;
+ childWindowClass.lpszMenuName = nullptr;
+ childWindowClass.lpszClassName = mChildClassName.c_str();
+ if (!RegisterClassExA(&childWindowClass))
+ {
+ return false;
+ }
+
+ DWORD parentStyle = WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU;
+ DWORD parentExtendedStyle = WS_EX_APPWINDOW;
+
+ RECT sizeRect = {0, 0, static_cast<LONG>(width), static_cast<LONG>(height)};
+ AdjustWindowRectEx(&sizeRect, parentStyle, FALSE, parentExtendedStyle);
+
+ mParentWindow = CreateWindowExA(parentExtendedStyle, mParentClassName.c_str(), name.c_str(),
+ parentStyle, CW_USEDEFAULT, CW_USEDEFAULT,
+ sizeRect.right - sizeRect.left, sizeRect.bottom - sizeRect.top,
+ nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+ mNativeWindow = CreateWindowExA(0, mChildClassName.c_str(), name.c_str(), WS_CHILD, 0, 0,
+ static_cast<int>(width), static_cast<int>(height),
+ mParentWindow, nullptr, GetModuleHandle(nullptr), this);
+
+ mNativeDisplay = GetDC(mNativeWindow);
+ if (!mNativeDisplay)
+ {
+ destroy();
+ return false;
+ }
+
+ return true;
+}
+
+void Win32Window::destroy()
+{
+ if (mNativeDisplay)
+ {
+ ReleaseDC(mNativeWindow, mNativeDisplay);
+ mNativeDisplay = 0;
+ }
+
+ if (mNativeWindow)
+ {
+ DestroyWindow(mNativeWindow);
+ mNativeWindow = 0;
+ }
+
+ if (mParentWindow)
+ {
+ DestroyWindow(mParentWindow);
+ mParentWindow = 0;
+ }
+
+ UnregisterClassA(mParentClassName.c_str(), nullptr);
+ UnregisterClassA(mChildClassName.c_str(), nullptr);
+}
+
+bool Win32Window::takeScreenshot(uint8_t *pixelData)
+{
+ if (mIsVisible)
+ {
+ return false;
+ }
+
+ bool error = false;
+
+ // Hack for DWM: There is no way to wait for DWM animations to finish, so we just have to wait
+ // for a while before issuing screenshot if window was just made visible.
+ {
+ static const double WAIT_WINDOW_VISIBLE_MS = 0.5; // Half a second for the animation
+ double timeSinceVisible = mSetVisibleTimer->getElapsedTime();
+
+ if (timeSinceVisible < WAIT_WINDOW_VISIBLE_MS)
+ {
+ Sleep(static_cast<DWORD>((WAIT_WINDOW_VISIBLE_MS - timeSinceVisible) * 1000));
+ }
+ }
+
+ HDC screenDC = nullptr;
+ HDC windowDC = nullptr;
+ HDC tmpDC = nullptr;
+ HBITMAP tmpBitmap = nullptr;
+
+ if (!error)
+ {
+ screenDC = GetDC(HWND_DESKTOP);
+ error = screenDC == nullptr;
+ }
+
+ if (!error)
+ {
+ windowDC = GetDC(mNativeWindow);
+ error = windowDC == nullptr;
+ }
+
+ if (!error)
+ {
+ tmpDC = CreateCompatibleDC(screenDC);
+ error = tmpDC == nullptr;
+ }
+
+ if (!error)
+ {
+ tmpBitmap = CreateCompatibleBitmap(screenDC, mWidth, mHeight);
+ error = tmpBitmap == nullptr;
+ }
+
+ POINT topLeft = {0, 0};
+ if (!error)
+ {
+ error = (MapWindowPoints(mNativeWindow, HWND_DESKTOP, &topLeft, 1) == 0);
+ }
+
+ if (!error)
+ {
+ error = SelectObject(tmpDC, tmpBitmap) == nullptr;
+ }
+
+ if (!error)
+ {
+ error = BitBlt(tmpDC, 0, 0, mWidth, mHeight, screenDC, topLeft.x, topLeft.y, SRCCOPY) == 0;
+ }
+
+ if (!error)
+ {
+ BITMAPINFOHEADER bitmapInfo;
+ bitmapInfo.biSize = sizeof(BITMAPINFOHEADER);
+ bitmapInfo.biWidth = mWidth;
+ bitmapInfo.biHeight = -mHeight;
+ bitmapInfo.biPlanes = 1;
+ bitmapInfo.biBitCount = 32;
+ bitmapInfo.biCompression = BI_RGB;
+ bitmapInfo.biSizeImage = 0;
+ bitmapInfo.biXPelsPerMeter = 0;
+ bitmapInfo.biYPelsPerMeter = 0;
+ bitmapInfo.biClrUsed = 0;
+ bitmapInfo.biClrImportant = 0;
+ int getBitsResult = GetDIBits(screenDC, tmpBitmap, 0, mHeight, pixelData,
+ reinterpret_cast<BITMAPINFO *>(&bitmapInfo), DIB_RGB_COLORS);
+ error = (getBitsResult == 0);
+ }
+
+ if (tmpBitmap != nullptr)
+ {
+ DeleteObject(tmpBitmap);
+ }
+ if (tmpDC != nullptr)
+ {
+ DeleteDC(tmpDC);
+ }
+ if (screenDC != nullptr)
+ {
+ ReleaseDC(nullptr, screenDC);
+ }
+ if (windowDC != nullptr)
+ {
+ ReleaseDC(mNativeWindow, windowDC);
+ }
+
+ return !error;
+}
+
+EGLNativeWindowType Win32Window::getNativeWindow() const
+{
+ return mNativeWindow;
+}
+
+EGLNativeDisplayType Win32Window::getNativeDisplay() const
+{
+ return mNativeDisplay;
+}
+
+void* Win32Window::getFramebufferNativeWindow() const
+{
+ return mNativeWindow;
+}
+
+void Win32Window::messageLoop()
+{
+ MSG msg;
+ while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+}
+
+void Win32Window::setMousePosition(int x, int y)
+{
+ RECT winRect;
+ GetClientRect(mNativeWindow, &winRect);
+
+ POINT topLeft;
+ topLeft.x = winRect.left;
+ topLeft.y = winRect.top;
+ ClientToScreen(mNativeWindow, &topLeft);
+
+ SetCursorPos(topLeft.x + x, topLeft.y + y);
+}
+
+OSWindow *CreateOSWindow()
+{
+ return new Win32Window();
+}
+
+bool Win32Window::setPosition(int x, int y)
+{
+ if (mX == x && mY == y)
+ {
+ return true;
+ }
+
+ RECT windowRect;
+ if (!GetWindowRect(mParentWindow, &windowRect))
+ {
+ return false;
+ }
+
+ if (!MoveWindow(mParentWindow, x, y, windowRect.right - windowRect.left,
+ windowRect.bottom - windowRect.top, TRUE))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool Win32Window::resize(int width, int height)
+{
+ if (width == mWidth && height == mHeight)
+ {
+ return true;
+ }
+
+ RECT windowRect;
+ if (!GetWindowRect(mParentWindow, &windowRect))
+ {
+ return false;
+ }
+
+ RECT clientRect;
+ if (!GetClientRect(mParentWindow, &clientRect))
+ {
+ return false;
+ }
+
+ LONG diffX = (windowRect.right - windowRect.left) - clientRect.right;
+ LONG diffY = (windowRect.bottom - windowRect.top) - clientRect.bottom;
+ if (!MoveWindow(mParentWindow, windowRect.left, windowRect.top, width + diffX, height + diffY,
+ TRUE))
+ {
+ return false;
+ }
+
+ if (!MoveWindow(mNativeWindow, 0, 0, width, height, FALSE))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void Win32Window::setVisible(bool isVisible)
+{
+ int flag = (isVisible ? SW_SHOW : SW_HIDE);
+
+ ShowWindow(mParentWindow, flag);
+ ShowWindow(mNativeWindow, flag);
+
+ if (isVisible)
+ {
+ mSetVisibleTimer->stop();
+ mSetVisibleTimer->start();
+ }
+}
+
+void Win32Window::pushEvent(Event event)
+{
+ OSWindow::pushEvent(event);
+
+ switch (event.Type)
+ {
+ case Event::EVENT_RESIZED:
+ MoveWindow(mNativeWindow, 0, 0, mWidth, mHeight, FALSE);
+ break;
+ default:
+ break;
+ }
+}
+
+void Win32Window::signalTestEvent()
+{
+ PostMessage(mNativeWindow, WM_USER, 0, 0);
+}
diff --git a/stream-servers/testlibs/windows/win32/Win32Window.h b/stream-servers/testlibs/windows/win32/Win32Window.h
new file mode 100644
index 0000000..71596c8
--- /dev/null
+++ b/stream-servers/testlibs/windows/win32/Win32Window.h
@@ -0,0 +1,60 @@
+//
+// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// Win32Window.h: Definition of the implementation of OSWindow for Win32 (Windows)
+
+#ifndef UTIL_WIN32_WINDOW_H
+#define UTIL_WIN32_WINDOW_H
+
+#include <windows.h>
+#include <string>
+
+#include "OSWindow.h"
+#include "Timer.h"
+
+class Win32Window : public OSWindow
+{
+ public:
+ Win32Window();
+ ~Win32Window() override;
+
+ bool initialize(const std::string &name, size_t width, size_t height) override;
+ void destroy() override;
+
+ bool takeScreenshot(uint8_t *pixelData) override;
+
+ EGLNativeWindowType getNativeWindow() const override;
+ EGLNativeDisplayType getNativeDisplay() const override;
+ void* getFramebufferNativeWindow() const override;
+
+ void messageLoop() override;
+
+ void pushEvent(Event event) override;
+
+ void setMousePosition(int x, int y) override;
+ bool setPosition(int x, int y) override;
+ bool resize(int width, int height) override;
+ void setVisible(bool isVisible) override;
+
+ void signalTestEvent() override;
+
+ private:
+ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
+
+ std::string mParentClassName;
+ std::string mChildClassName;
+
+ bool mIsVisible;
+ Timer *mSetVisibleTimer;
+
+ bool mIsMouseInWindow;
+
+ EGLNativeWindowType mNativeWindow;
+ EGLNativeWindowType mParentWindow;
+ EGLNativeDisplayType mNativeDisplay;
+};
+
+#endif // UTIL_WIN32_WINDOW_H
diff --git a/stream-servers/testlibs/x11/X11Window.cpp b/stream-servers/testlibs/x11/X11Window.cpp
new file mode 100644
index 0000000..2ebbd18
--- /dev/null
+++ b/stream-servers/testlibs/x11/X11Window.cpp
@@ -0,0 +1,610 @@
+//
+// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// X11Window.cpp: Implementation of OSWindow for X11
+
+#include "x11/X11Window.h"
+
+#include "base/System.h"
+
+#include <assert.h>
+
+namespace {
+
+Bool WaitForMapNotify(Display *dpy, XEvent *event, XPointer window)
+{
+ return event->type == MapNotify && event->xmap.window == reinterpret_cast<Window>(window);
+}
+
+static Key X11CodeToKey(Display *display, unsigned int scancode)
+{
+ int temp;
+ KeySym *keySymbols;
+ keySymbols = XGetKeyboardMapping(display, scancode, 1, &temp);
+
+ unsigned int keySymbol = keySymbols[0];
+ XFree(keySymbols);
+
+ switch (keySymbol)
+ {
+ case XK_Shift_L: return KEY_LSHIFT;
+ case XK_Shift_R: return KEY_RSHIFT;
+ case XK_Alt_L: return KEY_LALT;
+ case XK_Alt_R: return KEY_RALT;
+ case XK_Control_L: return KEY_LCONTROL;
+ case XK_Control_R: return KEY_RCONTROL;
+ case XK_Super_L: return KEY_LSYSTEM;
+ case XK_Super_R: return KEY_RSYSTEM;
+ case XK_Menu: return KEY_MENU;
+
+ case XK_semicolon: return KEY_SEMICOLON;
+ case XK_slash: return KEY_SLASH;
+ case XK_equal: return KEY_EQUAL;
+ case XK_minus: return KEY_DASH;
+ case XK_bracketleft: return KEY_LBRACKET;
+ case XK_bracketright:return KEY_RBRACKET;
+ case XK_comma: return KEY_COMMA;
+ case XK_period: return KEY_PERIOD;
+ case XK_backslash: return KEY_BACKSLASH;
+ case XK_asciitilde: return KEY_TILDE;
+ case XK_Escape: return KEY_ESCAPE;
+ case XK_space: return KEY_SPACE;
+ case XK_Return: return KEY_RETURN;
+ case XK_BackSpace: return KEY_BACK;
+ case XK_Tab: return KEY_TAB;
+ case XK_Page_Up: return KEY_PAGEUP;
+ case XK_Page_Down: return KEY_PAGEDOWN;
+ case XK_End: return KEY_END;
+ case XK_Home: return KEY_HOME;
+ case XK_Insert: return KEY_INSERT;
+ case XK_Delete: return KEY_DELETE;
+ case XK_KP_Add: return KEY_ADD;
+ case XK_KP_Subtract: return KEY_SUBTRACT;
+ case XK_KP_Multiply: return KEY_MULTIPLY;
+ case XK_KP_Divide: return KEY_DIVIDE;
+ case XK_Pause: return KEY_PAUSE;
+
+ case XK_F1: return KEY_F1;
+ case XK_F2: return KEY_F2;
+ case XK_F3: return KEY_F3;
+ case XK_F4: return KEY_F4;
+ case XK_F5: return KEY_F5;
+ case XK_F6: return KEY_F6;
+ case XK_F7: return KEY_F7;
+ case XK_F8: return KEY_F8;
+ case XK_F9: return KEY_F9;
+ case XK_F10: return KEY_F10;
+ case XK_F11: return KEY_F11;
+ case XK_F12: return KEY_F12;
+ case XK_F13: return KEY_F13;
+ case XK_F14: return KEY_F14;
+ case XK_F15: return KEY_F15;
+
+ case XK_Left: return KEY_LEFT;
+ case XK_Right: return KEY_RIGHT;
+ case XK_Down: return KEY_DOWN;
+ case XK_Up: return KEY_UP;
+
+ case XK_KP_Insert: return KEY_NUMPAD0;
+ case XK_KP_End: return KEY_NUMPAD1;
+ case XK_KP_Down: return KEY_NUMPAD2;
+ case XK_KP_Page_Down:return KEY_NUMPAD3;
+ case XK_KP_Left: return KEY_NUMPAD4;
+ case XK_KP_5: return KEY_NUMPAD5;
+ case XK_KP_Right: return KEY_NUMPAD6;
+ case XK_KP_Home: return KEY_NUMPAD7;
+ case XK_KP_Up: return KEY_NUMPAD8;
+ case XK_KP_Page_Up: return KEY_NUMPAD9;
+
+ case XK_a: return KEY_A;
+ case XK_b: return KEY_B;
+ case XK_c: return KEY_C;
+ case XK_d: return KEY_D;
+ case XK_e: return KEY_E;
+ case XK_f: return KEY_F;
+ case XK_g: return KEY_G;
+ case XK_h: return KEY_H;
+ case XK_i: return KEY_I;
+ case XK_j: return KEY_J;
+ case XK_k: return KEY_K;
+ case XK_l: return KEY_L;
+ case XK_m: return KEY_M;
+ case XK_n: return KEY_N;
+ case XK_o: return KEY_O;
+ case XK_p: return KEY_P;
+ case XK_q: return KEY_Q;
+ case XK_r: return KEY_R;
+ case XK_s: return KEY_S;
+ case XK_t: return KEY_T;
+ case XK_u: return KEY_U;
+ case XK_v: return KEY_V;
+ case XK_w: return KEY_W;
+ case XK_x: return KEY_X;
+ case XK_y: return KEY_Y;
+ case XK_z: return KEY_Z;
+
+ case XK_1: return KEY_NUM1;
+ case XK_2: return KEY_NUM2;
+ case XK_3: return KEY_NUM3;
+ case XK_4: return KEY_NUM4;
+ case XK_5: return KEY_NUM5;
+ case XK_6: return KEY_NUM6;
+ case XK_7: return KEY_NUM7;
+ case XK_8: return KEY_NUM8;
+ case XK_9: return KEY_NUM9;
+ case XK_0: return KEY_NUM0;
+ }
+
+ return Key(0);
+}
+
+static void AddX11KeyStateToEvent(Event *event, unsigned int state)
+{
+ event->Key.Shift = state & ShiftMask;
+ event->Key.Control = state & ControlMask;
+ event->Key.Alt = state & Mod1Mask;
+ event->Key.System = state & Mod4Mask;
+}
+
+}
+
+X11Window::X11Window()
+ : WM_DELETE_WINDOW(None),
+ WM_PROTOCOLS(None),
+ TEST_EVENT(None),
+ mDisplay(nullptr),
+ mWindow(0),
+ mRequestedVisualId(-1),
+ mVisible(false)
+{
+}
+
+X11Window::X11Window(int visualId)
+ : WM_DELETE_WINDOW(None),
+ WM_PROTOCOLS(None),
+ TEST_EVENT(None),
+ mDisplay(nullptr),
+ mWindow(0),
+ mRequestedVisualId(visualId),
+ mVisible(false)
+{
+}
+
+X11Window::~X11Window()
+{
+ destroy();
+}
+
+bool X11Window::initialize(const std::string &name, size_t width, size_t height)
+{
+ destroy();
+
+ mDisplay = XOpenDisplay(nullptr);
+ if (!mDisplay)
+ {
+ return false;
+ }
+
+ {
+ int screen = DefaultScreen(mDisplay);
+ Window root = RootWindow(mDisplay, screen);
+
+ Visual *visual;
+ if (mRequestedVisualId == -1)
+ {
+ visual = DefaultVisual(mDisplay, screen);
+ }
+ else
+ {
+ XVisualInfo visualTemplate;
+ visualTemplate.visualid = mRequestedVisualId;
+
+ int numVisuals = 0;
+ XVisualInfo *visuals = XGetVisualInfo(mDisplay, VisualIDMask, &visualTemplate, &numVisuals);
+ if (numVisuals <= 0)
+ {
+ return false;
+ }
+ assert(numVisuals == 1);
+
+ visual = visuals[0].visual;
+ XFree(visuals);
+ }
+
+ int depth = DefaultDepth(mDisplay, screen);
+ Colormap colormap = XCreateColormap(mDisplay, root, visual, AllocNone);
+
+ XSetWindowAttributes attributes;
+ unsigned long attributeMask = CWBorderPixel | CWColormap | CWEventMask;
+
+ attributes.event_mask = StructureNotifyMask | PointerMotionMask | ButtonPressMask |
+ ButtonReleaseMask | FocusChangeMask | EnterWindowMask |
+ LeaveWindowMask | KeyPressMask | KeyReleaseMask;
+ attributes.border_pixel = 0;
+ attributes.colormap = colormap;
+
+ mWindow = XCreateWindow(mDisplay, root, 0, 0, width, height, 0, depth, InputOutput,
+ visual, attributeMask, &attributes);
+ XFreeColormap(mDisplay, colormap);
+ }
+
+ if (!mWindow)
+ {
+ destroy();
+ return false;
+ }
+
+ // Tell the window manager to notify us when the user wants to close the
+ // window so we can do it ourselves.
+ WM_DELETE_WINDOW = XInternAtom(mDisplay, "WM_DELETE_WINDOW", False);
+ WM_PROTOCOLS = XInternAtom(mDisplay, "WM_PROTOCOLS", False);
+ if (WM_DELETE_WINDOW == None || WM_PROTOCOLS == None)
+ {
+ destroy();
+ return false;
+ }
+
+ if(XSetWMProtocols(mDisplay, mWindow, &WM_DELETE_WINDOW, 1) == 0)
+ {
+ destroy();
+ return false;
+ }
+
+ // Create an atom to identify our test event
+ TEST_EVENT = XInternAtom(mDisplay, "ANGLE_TEST_EVENT", False);
+ if (TEST_EVENT == None)
+ {
+ destroy();
+ return false;
+ }
+
+ XFlush(mDisplay);
+
+ mX = 0;
+ mY = 0;
+ mWidth = width;
+ mHeight = height;
+
+ return true;
+}
+
+void X11Window::destroy()
+{
+ if (mWindow)
+ {
+ XDestroyWindow(mDisplay, mWindow);
+ mWindow = 0;
+ }
+ if (mDisplay)
+ {
+ XCloseDisplay(mDisplay);
+ mDisplay = nullptr;
+ }
+ WM_DELETE_WINDOW = None;
+ WM_PROTOCOLS = None;
+}
+
+EGLNativeWindowType X11Window::getNativeWindow() const
+{
+ return mWindow;
+}
+
+EGLNativeDisplayType X11Window::getNativeDisplay() const
+{
+ return mDisplay;
+}
+
+void* X11Window::getFramebufferNativeWindow() const
+{
+ int screen = DefaultScreen(mDisplay);
+ Window root = RootWindow(mDisplay, screen);
+ return (void*)(uintptr_t)root;
+}
+
+void X11Window::messageLoop()
+{
+ int eventCount = XPending(mDisplay);
+ while (eventCount--)
+ {
+ XEvent event;
+ XNextEvent(mDisplay, &event);
+ processEvent(event);
+ }
+}
+
+void X11Window::setMousePosition(int x, int y)
+{
+ XWarpPointer(mDisplay, None, mWindow, 0, 0, 0, 0, x, y);
+}
+
+OSWindow *CreateOSWindow()
+{
+ return new X11Window();
+}
+
+bool X11Window::setPosition(int x, int y)
+{
+ XMoveWindow(mDisplay, mWindow, x, y);
+ XFlush(mDisplay);
+ return true;
+}
+
+bool X11Window::resize(int width, int height)
+{
+ XResizeWindow(mDisplay, mWindow, width, height);
+ XFlush(mDisplay);
+
+ // Wait until the window as actually been resized so that the code calling resize
+ // can assume the window has been resized.
+ const double kResizeWaitDelay = 0.2;
+ while (mHeight != height && mWidth != width)
+ {
+ messageLoop();
+ android::base::sleepMs(100);
+ }
+
+ return true;
+}
+
+void X11Window::setVisible(bool isVisible)
+{
+ if (mVisible == isVisible)
+ {
+ return;
+ }
+
+ if (isVisible)
+ {
+ XMapWindow(mDisplay, mWindow);
+
+ // Wait until we get an event saying this window is mapped so that the
+ // code calling setVisible can assume the window is visible.
+ // This is important when creating a framebuffer as the framebuffer content
+ // is undefined when the window is not visible.
+ XEvent dummyEvent;
+ XIfEvent(mDisplay, &dummyEvent, WaitForMapNotify, reinterpret_cast<XPointer>(mWindow));
+ }
+ else
+ {
+ XUnmapWindow(mDisplay, mWindow);
+ XFlush(mDisplay);
+ }
+ mVisible = isVisible;
+}
+
+void X11Window::signalTestEvent()
+{
+ XEvent event;
+ event.type = ClientMessage;
+ event.xclient.message_type = TEST_EVENT;
+ // Format needs to be valid or a BadValue is generated
+ event.xclient.format = 32;
+
+ // Hijack StructureNotifyMask as we know we will be listening for it.
+ XSendEvent(mDisplay, mWindow, False, StructureNotifyMask, &event);
+}
+
+void X11Window::processEvent(const XEvent &xEvent)
+{
+ // TODO(cwallez) text events
+ switch (xEvent.type)
+ {
+ case ButtonPress:
+ {
+ Event event;
+ MouseButton button = MOUSEBUTTON_UNKNOWN;
+ int wheelY = 0;
+
+ // The mouse wheel updates are sent via button events.
+ switch (xEvent.xbutton.button)
+ {
+ case Button4:
+ wheelY = 1;
+ break;
+ case Button5:
+ wheelY = -1;
+ break;
+ case 6:
+ break;
+ case 7:
+ break;
+
+ case Button1:
+ button = MOUSEBUTTON_LEFT;
+ break;
+ case Button2:
+ button = MOUSEBUTTON_MIDDLE;
+ break;
+ case Button3:
+ button = MOUSEBUTTON_RIGHT;
+ break;
+ case 8:
+ button = MOUSEBUTTON_BUTTON4;
+ break;
+ case 9:
+ button = MOUSEBUTTON_BUTTON5;
+ break;
+
+ default:
+ break;
+ }
+
+ if (wheelY != 0)
+ {
+ event.Type = Event::EVENT_MOUSE_WHEEL_MOVED;
+ event.MouseWheel.Delta = wheelY;
+ pushEvent(event);
+ }
+
+ if (button != MOUSEBUTTON_UNKNOWN)
+ {
+ event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
+ event.MouseButton.Button = button;
+ event.MouseButton.X = xEvent.xbutton.x;
+ event.MouseButton.Y = xEvent.xbutton.y;
+ pushEvent(event);
+ }
+ }
+ break;
+
+ case ButtonRelease:
+ {
+ Event event;
+ MouseButton button = MOUSEBUTTON_UNKNOWN;
+
+ switch (xEvent.xbutton.button)
+ {
+ case Button1:
+ button = MOUSEBUTTON_LEFT;
+ break;
+ case Button2:
+ button = MOUSEBUTTON_MIDDLE;
+ break;
+ case Button3:
+ button = MOUSEBUTTON_RIGHT;
+ break;
+ case 8:
+ button = MOUSEBUTTON_BUTTON4;
+ break;
+ case 9:
+ button = MOUSEBUTTON_BUTTON5;
+ break;
+
+ default:
+ break;
+ }
+
+ if (button != MOUSEBUTTON_UNKNOWN)
+ {
+ event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
+ event.MouseButton.Button = button;
+ event.MouseButton.X = xEvent.xbutton.x;
+ event.MouseButton.Y = xEvent.xbutton.y;
+ pushEvent(event);
+ }
+ }
+ break;
+
+ case KeyPress:
+ {
+ Event event;
+ event.Type = Event::EVENT_KEY_PRESSED;
+ event.Key.Code = X11CodeToKey(mDisplay, xEvent.xkey.keycode);
+ AddX11KeyStateToEvent(&event, xEvent.xkey.state);
+ pushEvent(event);
+ }
+ break;
+
+ case KeyRelease:
+ {
+ Event event;
+ event.Type = Event::EVENT_KEY_RELEASED;
+ event.Key.Code = X11CodeToKey(mDisplay, xEvent.xkey.keycode);
+ AddX11KeyStateToEvent(&event, xEvent.xkey.state);
+ pushEvent(event);
+ }
+ break;
+
+ case EnterNotify:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_ENTERED;
+ pushEvent(event);
+ }
+ break;
+
+ case LeaveNotify:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_LEFT;
+ pushEvent(event);
+ }
+ break;
+
+ case MotionNotify:
+ {
+ Event event;
+ event.Type = Event::EVENT_MOUSE_MOVED;
+ event.MouseMove.X = xEvent.xmotion.x;
+ event.MouseMove.Y = xEvent.xmotion.y;
+ pushEvent(event);
+ }
+ break;
+
+ case ConfigureNotify:
+ {
+ if (xEvent.xconfigure.width != mWidth || xEvent.xconfigure.height != mHeight)
+ {
+ Event event;
+ event.Type = Event::EVENT_RESIZED;
+ event.Size.Width = xEvent.xconfigure.width;
+ event.Size.Height = xEvent.xconfigure.height;
+ pushEvent(event);
+ }
+ if (xEvent.xconfigure.x != mX || xEvent.xconfigure.y != mY)
+ {
+ // Sometimes, the window manager reparents our window (for example
+ // when resizing) then the X and Y coordinates will be with respect to
+ // the new parent and not what the user wants to know. Use
+ // XTranslateCoordinates to get the coordinates on the screen.
+ int screen = DefaultScreen(mDisplay);
+ Window root = RootWindow(mDisplay, screen);
+
+ int x, y;
+ Window child;
+ XTranslateCoordinates(mDisplay, mWindow, root, 0, 0, &x, &y, &child);
+
+ if (x != mX || y != mY)
+ {
+ Event event;
+ event.Type = Event::EVENT_MOVED;
+ event.Move.X = x;
+ event.Move.Y = y;
+ pushEvent(event);
+ }
+ }
+ }
+ break;
+
+ case FocusIn:
+ if (xEvent.xfocus.mode == NotifyNormal || xEvent.xfocus.mode == NotifyWhileGrabbed)
+ {
+ Event event;
+ event.Type = Event::EVENT_GAINED_FOCUS;
+ pushEvent(event);
+ }
+ break;
+
+ case FocusOut:
+ if (xEvent.xfocus.mode == NotifyNormal || xEvent.xfocus.mode == NotifyWhileGrabbed)
+ {
+ Event event;
+ event.Type = Event::EVENT_LOST_FOCUS;
+ pushEvent(event);
+ }
+ break;
+
+ case DestroyNotify:
+ // We already received WM_DELETE_WINDOW
+ break;
+
+ case ClientMessage:
+ if (xEvent.xclient.message_type == WM_PROTOCOLS &&
+ static_cast<Atom>(xEvent.xclient.data.l[0]) == WM_DELETE_WINDOW)
+ {
+ Event event;
+ event.Type = Event::EVENT_CLOSED;
+ pushEvent(event);
+ }
+ else if (xEvent.xclient.message_type == TEST_EVENT)
+ {
+ Event event;
+ event.Type = Event::EVENT_TEST;
+ pushEvent(event);
+ }
+ break;
+ }
+}
diff --git a/stream-servers/testlibs/x11/X11Window.h b/stream-servers/testlibs/x11/X11Window.h
new file mode 100644
index 0000000..5da13d3
--- /dev/null
+++ b/stream-servers/testlibs/x11/X11Window.h
@@ -0,0 +1,55 @@
+//
+// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// X11Window.h: Definition of the implementation of OSWindow for X11
+
+#ifndef UTIL_X11_WINDOW_H
+#define UTIL_X11_WINDOW_H
+
+#include <string>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xresource.h>
+
+#include "OSWindow.h"
+
+class X11Window : public OSWindow
+{
+ public:
+ X11Window();
+ X11Window(int visualId);
+ ~X11Window();
+
+ bool initialize(const std::string &name, size_t width, size_t height) override;
+ void destroy() override;
+
+ EGLNativeWindowType getNativeWindow() const override;
+ EGLNativeDisplayType getNativeDisplay() const override;
+ void* getFramebufferNativeWindow() const override;
+
+ void messageLoop() override;
+
+ void setMousePosition(int x, int y) override;
+ bool setPosition(int x, int y) override;
+ bool resize(int width, int height) override;
+ void setVisible(bool isVisible) override;
+
+ void signalTestEvent() override;
+
+ private:
+ void processEvent(const XEvent &event);
+
+ Atom WM_DELETE_WINDOW;
+ Atom WM_PROTOCOLS;
+ Atom TEST_EVENT;
+
+ Display *mDisplay;
+ Window mWindow;
+ int mRequestedVisualId;
+ bool mVisible;
+};
+
+#endif // UTIL_X11_WINDOW_H