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