Import wlcs_1.2.1.orig.tar.xz
authorMike Gabriel <sunweaver@debian.org>
Fri, 26 Feb 2021 21:03:29 +0000 (21:03 +0000)
committerMike Gabriel <sunweaver@debian.org>
Fri, 26 Feb 2021 21:03:29 +0000 (21:03 +0000)
[dgit import orig wlcs_1.2.1.orig.tar.xz]

98 files changed:
.gitignore [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
COPYING.GPL2 [new file with mode: 0644]
COPYING.GPL3 [new file with mode: 0644]
README.rst [new file with mode: 0644]
bors.toml [new file with mode: 0644]
cmake/FindGtestGmock.cmake [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/source/format [new file with mode: 0644]
debian/upstream/signing-key.asc [new file with mode: 0644]
debian/watch [new file with mode: 0644]
example/mir_integration.cpp [new file with mode: 0644]
include/active_listeners.h [new file with mode: 0644]
include/data_device.h [new file with mode: 0644]
include/gtest_helpers.h [new file with mode: 0644]
include/gtk_primary_selection.h [new file with mode: 0644]
include/helpers.h [new file with mode: 0644]
include/in_process_server.h [new file with mode: 0644]
include/input_method.h [new file with mode: 0644]
include/layer_shell_v1.h [new file with mode: 0644]
include/mutex.h [new file with mode: 0644]
include/pointer_constraints_unstable_v1.h [new file with mode: 0644]
include/primary_selection.h [new file with mode: 0644]
include/relative_pointer_unstable_v1.h [new file with mode: 0644]
include/shared_library.h [new file with mode: 0644]
include/surface_builder.h [new file with mode: 0644]
include/version_specifier.h [new file with mode: 0644]
include/wl_handle.h [new file with mode: 0644]
include/wl_interface_descriptor.h [new file with mode: 0644]
include/wlcs/display_server.h [new file with mode: 0644]
include/wlcs/pointer.h [new file with mode: 0644]
include/wlcs/touch.h [new file with mode: 0644]
include/xdg_output_v1.h [new file with mode: 0644]
include/xdg_shell_stable.h [new file with mode: 0644]
include/xdg_shell_v6.h [new file with mode: 0644]
spread.yaml [new file with mode: 0644]
spread/build/alpine/task.yaml [new file with mode: 0644]
spread/build/fedora/task.yaml [new file with mode: 0644]
spread/build/ubuntu/task.yaml [new file with mode: 0644]
src/data_device.cpp [new file with mode: 0644]
src/gtk_primary_selection.cpp [new file with mode: 0644]
src/helpers.cpp [new file with mode: 0644]
src/in_process_server.cpp [new file with mode: 0644]
src/input_method.cpp [new file with mode: 0644]
src/layer_shell_v1.cpp [new file with mode: 0644]
src/main.cpp [new file with mode: 0644]
src/pointer_constraints_unstable_v1.cpp [new file with mode: 0644]
src/primary_selection.cpp [new file with mode: 0644]
src/protocol/gtk-primary-selection.xml [new file with mode: 0644]
src/protocol/pointer-constraints-unstable-v1.xml [new file with mode: 0644]
src/protocol/primary-selection-unstable-v1.xml [new file with mode: 0644]
src/protocol/relative-pointer-unstable-v1.xml [new file with mode: 0644]
src/protocol/wayland.xml [new file with mode: 0644]
src/protocol/wlr-foreign-toplevel-management-unstable-v1.xml [new file with mode: 0644]
src/protocol/wlr-layer-shell-unstable-v1.xml [new file with mode: 0644]
src/protocol/xdg-output-unstable-v1.xml [new file with mode: 0644]
src/protocol/xdg-shell-unstable-v6.xml [new file with mode: 0644]
src/protocol/xdg-shell.xml [new file with mode: 0644]
src/relative_pointer_unstable_v1.cpp [new file with mode: 0644]
src/shared_library.cpp [new file with mode: 0644]
src/surface_builder.cpp [new file with mode: 0644]
src/termcolor.hpp [new file with mode: 0644]
src/test_c_compile.c [new file with mode: 0644]
src/thread_proxy.h [new file with mode: 0644]
src/version_specifier.cpp [new file with mode: 0644]
src/xdg_output_v1.cpp [new file with mode: 0644]
src/xdg_shell_stable.cpp [new file with mode: 0644]
src/xdg_shell_v6.cpp [new file with mode: 0644]
src/xfail_supporting_test_listener.cpp [new file with mode: 0644]
src/xfail_supporting_test_listener.h [new file with mode: 0644]
tests/copy_cut_paste.cpp [new file with mode: 0644]
tests/frame_submission.cpp [new file with mode: 0644]
tests/gtk_primary_selection.cpp [new file with mode: 0644]
tests/pointer_constraints.cpp [new file with mode: 0644]
tests/primary_selection.cpp [new file with mode: 0644]
tests/relative_pointer.cpp [new file with mode: 0644]
tests/self_test.cpp [new file with mode: 0644]
tests/subsurfaces.cpp [new file with mode: 0644]
tests/surface_input_regions.cpp [new file with mode: 0644]
tests/test_bad_buffer.cpp [new file with mode: 0644]
tests/test_surface_events.cpp [new file with mode: 0644]
tests/touches.cpp [new file with mode: 0644]
tests/wl_output.cpp [new file with mode: 0644]
tests/wlr_foreign_toplevel_management_v1.cpp [new file with mode: 0644]
tests/wlr_layer_shell_v1.cpp [new file with mode: 0644]
tests/xdg_output_v1.cpp [new file with mode: 0644]
tests/xdg_popup.cpp [new file with mode: 0644]
tests/xdg_surface_stable.cpp [new file with mode: 0644]
tests/xdg_surface_v6.cpp [new file with mode: 0644]
tests/xdg_toplevel_stable.cpp [new file with mode: 0644]
tests/xdg_toplevel_v6.cpp [new file with mode: 0644]
tools/make_release_tarball [new file with mode: 0755]
wlcs.pc.in [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..05cadb2
--- /dev/null
@@ -0,0 +1,3 @@
+.idea/
+/build/
+/cmake-build-debug/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..6253e79
--- /dev/null
@@ -0,0 +1,82 @@
+dist: xenial
+os: linux
+version: ~> 1.0
+language: cpp
+
+branches:
+  only:
+  - staging
+  - trying
+  - master
+  # release branch
+  - /^release\/[\d\.]+$/
+  # release tag
+  - /^v[\d\.]+$/
+
+addons:
+  snaps:
+  -  lxd
+
+stages:
+- name: test
+- name: upload to ppa:mir-team
+  if: type = push
+      AND repo = MirServer/wlcs
+      AND ( branch = master
+            OR branch =~ ^release/[\d\.]+$
+            OR tag =~ ^v[\d\.]+$ )
+
+install:
+  - sudo adduser $USER lxd
+  - sudo apt remove --assume-yes lxd
+  - wget https://people.canonical.com/~chrishr/spread.snap
+  - sudo snap run lxd init --auto
+  - sudo snap install --dangerous spread.snap
+  - sudo snap connect spread:lxd lxd:lxd
+
+env:
+  global:
+  - DEBFULLNAME="Mir CI Bot"
+  - DEBEMAIL="mir-ci-bot@canonical.com"
+  jobs:
+  - SYSTEM=ubuntu-18.04 VARIANT=gcc
+  - SYSTEM=ubuntu-20.04 VARIANT=gcc
+  - SYSTEM=ubuntu-devel VARIANT=gcc
+  - SYSTEM=ubuntu-devel VARIANT=clang
+  - SYSTEM=fedora-31 VARIANT=gcc
+  - SYSTEM=fedora-32 VARIANT=gcc
+  - SYSTEM=alpine-3.11 VARIANT=gcc
+
+script:
+  - sg lxd -c 'LXD_DIR=/var/snap/lxd/common/lxd snap run spread lxd:$SYSTEM:...:$VARIANT'
+
+jobs:
+  include:
+  - &ppa-upload
+    stage: upload to ppa:mir-team
+    env: RELEASE=18.04
+    git:
+      depth: false
+    before_install: &decrypt-bot-data
+    - openssl aes-256-cbc -K $encrypted_0102c5bea602_key -iv $encrypted_0102c5bea602_iv -in tools/bot-data.tar.xz.enc -out /tmp/bot-data.tar.xz -d
+    - tar --verbose --extract --xz --file /tmp/bot-data.tar.xz --directory ${HOME}
+    # We don't need to do the install steps
+    install: skip
+    script:
+    - tools/ppa-upload.sh
+    addons:
+      apt:
+        packages:
+        - debhelper
+        - devscripts
+        - dput
+        - fakeroot
+        - python-bzrlib
+        - python-launchpadlib
+        - python-paramiko
+        - libdistro-info-perl
+  - <<: *ppa-upload
+    env: RELEASE=20.04
+  - <<: *ppa-upload
+    env: RELEASE=devel
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..de550f5
--- /dev/null
@@ -0,0 +1,277 @@
+# Copyright © 2017 Canonical Ltd.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or 3 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Authored by: Thomas Voss <thomas.voss@canonical.com>,
+#              Alan Griffiths <alan@octopull.co.uk>,
+#              Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+
+cmake_minimum_required(VERSION 3.5)
+
+cmake_policy(SET CMP0015 NEW)
+cmake_policy(SET CMP0022 NEW)
+
+project(wlcs VERSION 1.2.1)
+
+add_definitions(-D_GNU_SOURCE)
+add_definitions(-D_FILE_OFFSET_BITS=64)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
+include(GNUInstallDirs)
+find_package(PkgConfig)
+#include (Doxygen.cmake)
+
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -g -Werror -Wall -pedantic -Wextra -fPIC")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -g -std=c++14 -Werror -Wall -fno-strict-aliasing -pedantic -Wnon-virtual-dtor -Wextra -fPIC")
+if ("${CMAKE_SHARED_LINKER_FLAGS}" MATCHES ".*-fsanitize=.*")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
+else()
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed -Wl,--no-undefined")
+endif()
+
+include(CheckCXXCompilerFlag)
+check_cxx_compiler_flag(-Wgnu-zero-variadic-macro-arguments HAVE_W_GNU_VARIADIC_MACROS)
+if (HAVE_W_GNU_VARIADIC_MACROS)
+  # GTest's parametrised test macro tweaks this warning
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=gnu-zero-variadic-macro-arguments")
+endif ()
+
+include(CheckCXXSymbolExists)
+list(APPEND CMAKE_REQUIRED_HEADERS ${GTEST_INCLUDE_DIRECTORIES})
+check_cxx_symbol_exists(INSTANTIATE_TEST_SUITE_P "gtest/gtest.h" HAVE_INSTANTIATE_TEST_SUITE_P)
+if (NOT HAVE_INSTANTIATE_TEST_SUITE_P)
+  #GTest conveniently renamed INSTANTIATE_TEST_CASE_P and then deprecated it.
+  add_definitions(-DINSTANTIATE_TEST_SUITE_P=INSTANTIATE_TEST_CASE_P)
+endif()
+
+find_package(Boost)
+find_package(GtestGmock)
+pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
+pkg_check_modules(WAYLAND_SERVER REQUIRED wayland-server)
+pkg_check_modules(WAYLAND_SCANNER REQUIRED wayland-scanner)
+
+include_directories(include)
+include_directories(${Boost_INCLUDE_DIRS})
+include_directories(${GMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIR})
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+set(PROTOCOL_SOURCES "")
+
+execute_process(
+  COMMAND wayland-scanner --version
+  OUTPUT_VARIABLE WAYLAND_SCANNER_VERSION_OUTPUT
+  ERROR_VARIABLE WAYLAND_SCANNER_VERSION_OUTPUT
+)
+
+separate_arguments(WAYLAND_SCANNER_VERSION_OUTPUT)
+list(LENGTH WAYLAND_SCANNER_VERSION_OUTPUT VERSION_STRING_COMPONENTS)
+list(GET WAYLAND_SCANNER_VERSION_OUTPUT 1 VERSION_STRING)
+string(STRIP ${VERSION_STRING} VERSION_STRING)
+
+if (NOT(VERSION_STRING_COMPONENTS EQUAL 2))
+  message(AUTHOR_WARNING "Failed to parse wayland-scanner --version output")
+endif()
+
+if (VERSION_STRING VERSION_GREATER 1.14.91)
+  message(STATUS "Found wayland-scanner version ${VERSION_STRING}, using private-code mode")
+  set(WAYLAND_SCANNER_CODE_GENERATION_TYPE "private-code")
+else()
+  message(STATUS "Found wayland-scanner version ${VERSION_STRING}, using (old) code mode")
+  set(WAYLAND_SCANNER_CODE_GENERATION_TYPE "code")
+endif()
+
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated)
+
+macro(GENERATE_PROTOCOL PROTOCOL_NAME)
+  set(PROTOCOL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/protocol/${PROTOCOL_NAME}.xml")
+  set(OUTPUT_PATH_C "${CMAKE_CURRENT_BINARY_DIR}/generated/${PROTOCOL_NAME}.c")
+  set(OUTPUT_PATH_SERVER_H "${CMAKE_CURRENT_BINARY_DIR}/generated/${PROTOCOL_NAME}-server.h")
+  set(OUTPUT_PATH_CLIENT_H "${CMAKE_CURRENT_BINARY_DIR}/generated/${PROTOCOL_NAME}-client.h")
+  add_custom_command(OUTPUT "${OUTPUT_PATH_C}"
+    VERBATIM
+    COMMAND "wayland-scanner" "--include-core-only" ${WAYLAND_SCANNER_CODE_GENERATION_TYPE} "${PROTOCOL_PATH}" "${OUTPUT_PATH_C}"
+    DEPENDS "${PROTOCOL_PATH}"
+  )
+  add_custom_command(OUTPUT "${OUTPUT_PATH_SERVER_H}"
+    VERBATIM
+    COMMAND "wayland-scanner" "--include-core-only" "server-header" "${PROTOCOL_PATH}" "${OUTPUT_PATH_SERVER_H}"
+    DEPENDS "${PROTOCOL_PATH}"
+  )
+  add_custom_command(OUTPUT "${OUTPUT_PATH_CLIENT_H}"
+    VERBATIM
+    COMMAND "wayland-scanner" "--include-core-only" "client-header" "${PROTOCOL_PATH}" "${OUTPUT_PATH_CLIENT_H}"
+    DEPENDS "${PROTOCOL_PATH}"
+  )
+  list(APPEND PROTOCOL_SOURCES ${OUTPUT_PATH_C} ${OUTPUT_PATH_CLIENT_H})
+endmacro()
+
+GENERATE_PROTOCOL(gtk-primary-selection)
+GENERATE_PROTOCOL(primary-selection-unstable-v1)
+GENERATE_PROTOCOL(wayland)
+GENERATE_PROTOCOL(xdg-shell-unstable-v6)
+GENERATE_PROTOCOL(xdg-shell)
+GENERATE_PROTOCOL(wlr-layer-shell-unstable-v1)
+GENERATE_PROTOCOL(xdg-output-unstable-v1)
+GENERATE_PROTOCOL(wlr-foreign-toplevel-management-unstable-v1)
+GENERATE_PROTOCOL(pointer-constraints-unstable-v1)
+GENERATE_PROTOCOL(relative-pointer-unstable-v1)
+
+option(WLCS_BUILD_ASAN "Build a test runner with AddressSanitizer annotations" ON)
+option(WLCS_BUILD_TSAN "Build a test runner with ThreadSanitizer annotations" ON)
+option(WLCS_BUILD_UBSAN "Build a test runner with UndefinedBehaviourSanitizer annotations" ON)
+
+set(
+  WLCS_SOURCES
+
+  include/wlcs/display_server.h
+  include/wlcs/pointer.h
+  include/wlcs/touch.h
+  include/helpers.h
+  include/wl_handle.h
+  include/in_process_server.h
+  include/pointer_constraints_unstable_v1.h
+  include/primary_selection.h
+  include/relative_pointer_unstable_v1.h
+  include/xdg_shell_v6.h
+  include/xdg_shell_stable.h
+  include/xdg_output_v1.h
+  include/version_specifier.h
+  include/wl_interface_descriptor.h
+  include/layer_shell_v1.h
+  include/surface_builder.h
+  include/input_method.h
+
+  src/data_device.cpp
+  src/gtk_primary_selection.cpp
+  src/helpers.cpp
+  src/in_process_server.cpp
+  src/xdg_shell_v6.cpp
+  src/xdg_shell_stable.cpp
+  src/layer_shell_v1.cpp
+  src/main.cpp
+  src/pointer_constraints_unstable_v1.cpp
+  src/primary_selection.cpp
+  src/shared_library.cpp
+  src/relative_pointer_unstable_v1.cpp
+  src/xfail_supporting_test_listener.h
+  src/xfail_supporting_test_listener.cpp
+  src/termcolor.hpp
+  src/thread_proxy.h
+  src/xdg_output_v1.cpp
+  src/version_specifier.cpp
+  src/surface_builder.cpp
+  src/input_method.cpp
+
+  ${PROTOCOL_SOURCES}
+
+  tests/test_bad_buffer.cpp
+  tests/pointer_constraints.cpp
+  tests/copy_cut_paste.cpp
+  tests/gtk_primary_selection.cpp
+  tests/test_surface_events.cpp
+  tests/touches.cpp
+  tests/wl_output.cpp
+  tests/surface_input_regions.cpp
+  tests/frame_submission.cpp
+  tests/primary_selection.cpp
+  tests/relative_pointer.cpp
+  tests/subsurfaces.cpp
+  tests/xdg_surface_v6.cpp
+  tests/xdg_toplevel_v6.cpp
+  tests/xdg_surface_stable.cpp
+  tests/xdg_toplevel_stable.cpp
+  tests/xdg_popup.cpp
+  tests/wlr_layer_shell_v1.cpp
+  tests/xdg_output_v1.cpp
+  tests/wlr_foreign_toplevel_management_v1.cpp
+  tests/self_test.cpp
+)
+
+add_library(test_c_compile OBJECT src/test_c_compile.c)
+
+add_executable(wlcs ${WLCS_SOURCES})
+set(EXECUTABLE_TARGETS wlcs)
+if (WLCS_BUILD_ASAN)
+  add_executable(wlcs.asan ${WLCS_SOURCES})
+  target_compile_options(wlcs.asan PUBLIC -fsanitize=address -fno-omit-frame-pointer)
+  set_target_properties(
+    wlcs.asan
+    PROPERTIES
+    LINK_FLAGS -fsanitize=address)
+
+  # Explicitly linking with the sanitiser is harmless
+  target_link_libraries(wlcs.asan asan)
+  list(APPEND EXECUTABLE_TARGETS wlcs.asan)
+endif()
+
+if (WLCS_BUILD_TSAN)
+  add_executable(wlcs.tsan ${WLCS_SOURCES})
+  target_compile_options(wlcs.tsan PUBLIC -fsanitize=thread -fno-omit-frame-pointer)
+  set_target_properties(
+    wlcs.tsan
+    PROPERTIES
+    LINK_FLAGS -fsanitize=thread)
+  # Explicitly linking with the sanitiser is harmless.
+  target_link_libraries(wlcs.tsan tsan)
+  list(APPEND EXECUTABLE_TARGETS wlcs.tsan)
+endif()
+
+if (WLCS_BUILD_UBSAN)
+  add_executable(wlcs.ubsan ${WLCS_SOURCES})
+  target_compile_options(wlcs.ubsan PUBLIC -fsanitize=undefined)
+  set_target_properties(
+    wlcs.ubsan
+    PROPERTIES
+    LINK_FLAGS -fsanitize=undefined)
+
+  # Unsure quite why explicitly linking with ubsan is required, but here we are…
+  target_link_libraries(wlcs.ubsan ubsan)
+  list(APPEND EXECUTABLE_TARGETS wlcs.ubsan)
+endif()
+
+foreach(TARGET IN LISTS EXECUTABLE_TARGETS)
+  target_link_libraries(
+    ${TARGET}
+
+    ${WAYLAND_CLIENT_LDFLAGS} ${WAYLAND_CLIENT_LIBRARIES}
+    ${WAYLAND_SERVER_LDFLAGS} ${WAYLAND_CLIENT_LIBRARIES}
+    dl
+
+    ${GMOCK_LIBRARY}
+    ${GTEST_LIBRARY}
+  )
+endforeach(TARGET)
+
+install(
+  TARGETS ${EXECUTABLE_TARGETS}
+  RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/wlcs
+)
+
+install(
+  DIRECTORY include/wlcs
+  DESTINATION ${CMAKE_INSTALL_PREFIX}/include/
+)
+
+configure_file(
+  wlcs.pc.in
+  ${CMAKE_CURRENT_BINARY_DIR}/wlcs.pc
+  @ONLY
+)
+
+install(
+  FILES
+    ${CMAKE_CURRENT_BINARY_DIR}/wlcs.pc
+  DESTINATION
+    ${CMAKE_INSTALL_LIBDIR}/pkgconfig/
+)
diff --git a/COPYING.GPL2 b/COPYING.GPL2
new file mode 100644 (file)
index 0000000..d159169
--- /dev/null
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/COPYING.GPL3 b/COPYING.GPL3
new file mode 100644 (file)
index 0000000..4432540
--- /dev/null
@@ -0,0 +1,676 @@
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                      TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+  
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                    END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..50f8ec1
--- /dev/null
@@ -0,0 +1,52 @@
+==============================
+Wayland Conformance Test Suite
+==============================
+
+``wlcs`` aspires to be a protocol-conformance-verifying test suite usable by
+Wayland compositor implementors.
+
+It is growing out of porting the existing Weston test suite to be run
+in `Mir's <https://github.com/MirServer/mir>`_ test suite,
+but it is designed to be usable by any compositor.
+
+There have been a number of previous attempts at a Wayland compositor test
+suite - the `Wayland Functional Integration Test Suite <https://github.com/intel/wayland-fits>`_,
+and the `Weston test suite <https://wayland.freedesktop.org/testing.html#heading_toc_j_3>`_
+are a couple of examples.
+
+What sets ``wlcs`` apart from the graveyard of existing test suites is its
+integration method.
+
+Previous test suites have used a Wayland protocol extension
+to interrogate the compositor state. This obviously requires that compositors
+implement that protocol, means that tests only have access to information
+provided by that protocol, and the IPC means that there isn't a canonical
+happens-before ordering available.
+
+Instead, ``wlcs`` relies on compositors providing an integration module,
+providing ``wlcs`` with API hooks to start a compositor, connect a client,
+move a window, and so on. This makes both writing aid debugging tests easier -
+the tests are (generally) in the same address space as the compositor, so there
+is a consistent global clock available, it's easier to poke around in
+compositor internals, and standard debugging tools can follow control flow from
+the test client to the compositor and back again.
+
+Usage
+-----
+
+In order for ``wlcs`` to test your compositor you need to provide an
+integration module. This needs to implement the interfaces found in
+``include/wlcs``. If your integration module is
+``awesome_compositor_wlcs_integration.so``, then running ``wlcs
+awesome_compositor_wlcs_integration.so`` will load and run all the tests.
+
+Development
+-----------
+
+``wlcs`` requires a small number of hooks into your compositor to drive the
+tests - it needs to know how to start and stop the mainloop, to get an fd to
+connect a client to, and so on.
+
+To access these hooks, ``wlcs`` looks for a symbol ``wlcs_server_integration``
+of type ``struct WlcsServerIntegration const`` (defined in
+``include/wlcs/display_server.h``) in your integration module.
diff --git a/bors.toml b/bors.toml
new file mode 100644 (file)
index 0000000..c6e04bd
--- /dev/null
+++ b/bors.toml
@@ -0,0 +1,4 @@
+status = [ "continuous-integration/travis-ci/push" ]
+block-labels = [ "no-merge" ]
+timeout-sec = 12000
+delete-merged-branches = true
diff --git a/cmake/FindGtestGmock.cmake b/cmake/FindGtestGmock.cmake
new file mode 100644 (file)
index 0000000..230d988
--- /dev/null
@@ -0,0 +1,69 @@
+include(FindPackageHandleStandardArgs)
+
+find_package(GTest)
+
+if (NOT GTEST_FOUND)
+    include(ExternalProject)
+
+    find_path(GTEST_ROOT
+        NAMES CMakeLists.txt
+        PATHS /usr/src/gtest /usr/src/googletest/googletest/
+        DOC "Path to GTest CMake project")
+
+    ExternalProject_Add(GTest PREFIX ./gtest
+            SOURCE_DIR ${GTEST_ROOT}
+            CMAKE_ARGS
+                -DCMAKE_CXX_COMPILER_WORKS=1
+                -DCMAKE_CXX_FLAGS='${CMAKE_CXX_FLAGS}'
+                -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
+            INSTALL_COMMAND true
+            BUILD_BYPRODUCTS
+                ${CMAKE_CURRENT_BINARY_DIR}/gtest/src/GTest-build/libgtest.a
+                ${CMAKE_CURRENT_BINARY_DIR}/gtest/src/GTest-build/libgtest_main.a
+                ${CMAKE_CURRENT_BINARY_DIR}/gtest/src/GMock-build/libgmock.a)
+
+    ExternalProject_Get_Property(GTest binary_dir)
+
+    add_library(gtest UNKNOWN IMPORTED)
+    set_target_properties(gtest      PROPERTIES IMPORTED_LOCATION ${binary_dir}/libgtest.a)
+    add_dependencies(gtest GTest)
+    set(GTEST_LIBRARY "gtest")
+
+    add_library(gtest_main UNKNOWN IMPORTED)
+    set_target_properties(gtest_main PROPERTIES IMPORTED_LOCATION ${binary_dir}/libgtest_main.a)
+    add_dependencies(gtest_main GTest)
+    set(GTEST_MAIN_LIBRARY "gtest_main")
+
+    set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY})
+    find_path(GTEST_INCLUDE_DIRS NAMES gtest/gtest.h)
+    find_package_handle_standard_args(GTest GTEST_LIBRARY GTEST_BOTH_LIBRARIES GTEST_INCLUDE_DIRS)
+endif()
+
+find_file(GMOCK_SOURCE
+        NAMES gmock-all.cc
+        DOC "GMock source"
+        PATHS /usr/src/googletest/googlemock/src/ /usr/src/gmock/ /usr/src/gmock/src)
+
+if (EXISTS ${GMOCK_SOURCE})
+    find_path(GMOCK_INCLUDE_DIR gmock/gmock.h PATHS /usr/src/googletest/googlemock/include)
+
+    add_library(GMock STATIC ${GMOCK_SOURCE})
+
+    if (EXISTS /usr/src/googletest/googlemock/src)
+        set_source_files_properties(${GMOCK_SOURCE} PROPERTIES COMPILE_FLAGS "-I/usr/src/googletest/googlemock")
+    endif()
+
+    if (EXISTS /usr/src/gmock/src)
+        set_source_files_properties(${GMOCK_SOURCE} PROPERTIES COMPILE_FLAGS "-I/usr/src/gmock")
+    endif()
+
+    find_package_handle_standard_args(GMock DEFAULT_MSG GMOCK_INCLUDE_DIR)
+
+    set(GMOCK_LIBRARY GMock)
+else()
+    # Assume gmock is no longer source, we'll find out soon enough if that's wrong
+    add_custom_target(GMock)
+    string(REPLACE gtest gmock GMOCK_LIBRARY ${GTEST_LIBRARY})
+endif()
+
+set(GMOCK_LIBRARIES ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARY})
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..a4be7f1
--- /dev/null
@@ -0,0 +1,38 @@
+wlcs (1.2.1-0ubuntu0) groovy; urgency=medium
+
+  * New upstream release. Notable changes:
+    + Fix cut & paste test
+
+ -- Alan Griffiths <alan@octopull.co.uk>  Fri, 19 Feb 2021 11:11:11 +0000
+
+wlcs (1.2.0-0ubuntu0) groovy; urgency=medium
+
+  * New upstream release. Notable changes:
+    + Add tests for wlr_layer_shell_unstable_v1
+    + Build fixes for Musl libc; WLCS now builds on Alpine. Thanks, Luca Weiss
+    + More XDG Shell tests, particularly around protocol errors,
+    window-geometry, and input edge-cases.
+    + Add tests for wlr_foreign_toplevel_management_unstable_v1
+    + Many improvements to wl_subsurface tests. Notably this fixes a
+    misinterpretation of the protocol which lead to testing incorrect
+    behaviour.
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com>  Mon, 31 Aug 2020 16:39:21 +1000
+
+wlcs (1.1.0-0ubuntu0) eoan; urgency=medium
+
+  * New upstream release. Relevant upstream changes:
+    + Document the compositor-integration version macros
+    + Add tests for the wl_output protocol
+    + More tests for XDG Shell, particularly around popups and window movement
+    + Add tests for wp_primary_selection_unstable_v1 protocol
+    + Add tests for gdk_primary_selection protocol
+    + Lots of build fixes for !Ubuntu systems. Thanks, Neal Gompa!
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com>  Tue, 23 Jul 2019 10:37:52 +1000
+
+wlcs (1.0-0ubuntu0) disco; urgency=medium
+
+  * Initial release
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com>  Tue, 08 Jan 2019 11:32:06 +1100
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..f599e28
--- /dev/null
@@ -0,0 +1 @@
+10
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..e65efd8
--- /dev/null
@@ -0,0 +1,29 @@
+Source: wlcs
+Priority: optional
+Maintainer: Christopher James Halse Rogers <raof@ubuntu.com>
+Build-Depends:
+ debhelper (>= 9),
+ cmake,
+ libboost-dev,
+ libgtest-dev,
+ libwayland-dev,
+ pkg-config,
+ google-mock
+Standards-Version: 4.1.3
+Section: devel
+Homepage: https://github.com/MirServer/wlcs
+Vcs-Browser: https://salsa.debian.org/mir-server-team/wlcs
+Vcs-Git: https://salsa.debian.org/mir-server-team/wlcs.git
+
+Package: wlcs
+Section: devel
+Architecture: any
+Multi-Arch: same
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Wayland Conformance Suite's
+ wlcs aspires to be a protocol-conformance-verifying test suite usable by
+ Wayland compositor implementations.
+ .
+ This package contains the headers necessary for a Wayland compositor to
+ provide the integration module needed to run wlcs tests, and the test
+ runner binary needed to run the tests against the compositor.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..fa4f094
--- /dev/null
@@ -0,0 +1,73 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: wlcs
+Source: https://github.com/MirServer/wlcs
+
+Files: *
+Copyright: 2017-2019 Canonical, Ltd
+License: GPL-3
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 3 as
+ published by the Free Software Foundation.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 3 can be found in the file
+ `/usr/share/common-licenses/GPL-3'.
+
+Files: tests/test_bad_buffer.cpp
+ tests/test_surface_events.cpp
+ xdg_popup_stable.cpp
+ xdg_popup_v6.cpp
+ xdg_surface_stable.cpp
+ xdg_surface_v6.cpp
+ xdg_toplevel_stable.cpp
+ xdg_toplevel_v6.cpp
+Copyright: 2012 Intel Corporation
+ 2013 Collabora, Ltd.
+ 2017-2018 Canonical Ltd.
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+ .
+ The above copyright notice and this permission notice (including the
+ next paragraph) shall be included in all copies or substantial
+ portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+
+Files: debian/*
+Copyright: 2019 Christopher James Halse Rogers <raof@ubuntu.com>
+License: GPL-2+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..2791285
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/make -f
+
+include /usr/share/dpkg/default.mk
+
+# see FEATURE AREAS in dpkg-buildflags(1)
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+COMMON_CONFIGURE_OPTIONS =\
+  -DWLCS_BUILD_ASAN=ON \
+  -DWLCS_BUILD_UBSAN=ON \
+  -DWLCS_BUILD_TSAN=ON
+
+ifeq ($(filter i386 armhf, $(DEB_HOST_ARCH)),)
+  ifeq ($(DEB_DISTRIBUTION),xenial)
+    ifneq ($(filter arm64 ppc64el, $(DEB_HOST_ARCH)),)
+      # arm64 on 16.04 does not have tsan
+      COMMON_CONFIGURE_OPTIONS += -DWLCS_BUILD_TSAN=OFF
+    endif
+  endif
+else
+  # i386 and armhf do not have tsan
+  COMMON_CONFIGURE_OPTIONS += -DWLCS_BUILD_TSAN=OFF
+endif
+
+override_dh_auto_configure:
+       dh_auto_configure -- $(COMMON_CONFIGURE_OPTIONS)
+
+%:
+       dh $@
diff --git a/debian/source/format b/debian/source/format
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc
new file mode 100644 (file)
index 0000000..f538fd7
--- /dev/null
@@ -0,0 +1,366 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFmTm/8BEACni7C38F6eITa90TlDHp1m5zMzF1ZMAjTJpiV43KRubeDnYmoo
+HUkH0TtXlA5KY/LotWE8IupNtrf1c+17JvbhYYH+gKg3rLdHt2hinQxoNT4ncalt
+BzTvq0YOrQGba6uSq4FMjbIhRMPjQLLWxnn3abtIh33y9d9HPMYRF1SG/VliLiwp
+KC0zdiHssMFMaZwqSNowhffR4MZCcijxZbyJ1/2UbnK4u7Y6p0VkkmyNmlPXVPFn
+cfbxJohaUwj/cphVDJQ0pDZm0FnNDJzfYU8E7Cx5uBEGOauAGj1vkHeVZ9SG512u
+F6T1rFj5eaGWcsjIkIuTq5/i6FJf+tvod7/Ugpjf98KZ40Gf/m6iaZErzWg225II
+9A7L/1C+Izy3orvxKeCuBCiGo2kzW+POcdp/h2Oc9cGAu88W8OcBSaxxxebsggV4
+3Zn9kxA5ZmTnc/2AOOHvN6GcvDASvAHUq8yZSmAdiCtkimlpJ9G9v54Y+vc8Ptuu
+hqV/0uL21qFDxBeh7h6QTS9V+aZF98SbYra0dW1ufYMwHn0Vv33N3XuY0Tf+E9bi
+Fc/TLDl1yQMWzl/mq9PNzyZMMUF4bmNyLX64GhJIXQ0ZAEh/S/y8TMpehjP/KK2e
+WnXUQiWgNXwPxra6mk24K2k2LCHCVxan+nt5cyrETr8VxMJ0bcpMaG9CXwARAQAB
+tDBDaHJpc3RvcGhlciBKYW1lcyBIYWxzZSBSb2dlcnMgPHJhb2ZAdWJ1bnR1LmNv
+bT6JAhwEEAEKAAYFAlmo+VQACgkQGLP1D+WSPBiqlQ/+LitTooxk3zjg4xo3P7cf
+enG1mF1fu/JonyLT+m5mJJ064ZiZ0ywldT5zwmHTFQAr2Z5S6aNg4o8MmeA9JjpL
+LAX1kclD7a2/o33vyoRfE2EabYQkVI0qJgG0YxP0Uip070p6kBZHc9CsvBxiWn5c
+k8bIW+LfEQwPT3HtvYFUBmFsrayHifvyV0nWfGKVzNHcpwdYqSHNKjM8EeY9lunv
+/zNJMRUTPfhfD05FiyaUXCJyadXJ+IQxrG/wo7hKQcofopZQyMNZrT4LKq7KqFV9
+5tLaS74GzFX3L6m0cVlsGP0UrBWUPemex7Ui4gIbdUG/gRy4I5PS6D79ADoH0vIH
+1IKd095LQcudIgCGZyUQabFx6vIC8FMbqK49jndFk6ZbmBZXRWxKK0M2B4Bnlp87
++dJKXcvN5/lbt/7X3U3Q1IjbzCjPST7Rr+LvPpOK8RxrPwNBYa7ogrZWSoM5uPN4
+1jBDnVQiQ024LL9EMtr8IL1NjIFZHmRraokv8F+8Vk/MB8sxzQRl4Kuhj/Q/vVJY
+UeijijhAyzEBoYN45BNVENq9NaMeeeArYeIrsX8QhbqMSB790ZuUgNaDemz6WDgL
+wdybapwmOrWy/r8gHFox7ldkuSZS9vKGGrOJhS2vsnV0L95iEy4irm5iY/fqCW9n
+Y8xtzwQ2wZLUKCnCN65BfTeJAjoEEwEKACQFAlmToAoCGwMFCQGLggAICwkIBw0M
+CwoFFQoJCAsCHgECF4AACgkQoF7vcIbXMkKW6g/+ModoGKhi52wClyQnaMUzYVgB
+xyO5iqJHLC+D90FN0fIpL0R6sTUwZ4ac/ymg9PfjZeoPtDcbFI0C93M+isCnoo9r
+/IJsanSaoZhLJKlZ5XrLuQg/TRqMhMP+/xmjl9c0zkYFnTS5gdaTiSGhqn1OLoFM
+z01fixlf3Q/Ej9Auw+YUE6bCMMqh3bbh4qInfd0o8GrvubtMeekhzasH2avQvrfV
+zSx/WKf3Sz4Np4iq943fxDgswbeCZcAliFBEZ+wME4/+7LXW3DrZCMEY81USv1T0
+c+bIkI+A5Dupi1+iWqUfiQyVW6nocU+Hj6NjA0PfYJxeJX8+0xzB1m4FyJklUaJ/
+OEEs5NA5+8iQbSFDAnqb376IQJX/P2clEyR3oGkzZOFGYNJcNJpLxk/BBgkPYZ2v
+LsC3eXykA81S7mbpiWIGUlEL9/LAIdEVYLiBp1+XzSwsPa3ME6y3OQHXLjrEPeYD
+AKZWBZDkYF/F2IxFiCSJ6P+gzQD67xOPF7cxsG/iIeVuLvxDylkrPweYyTgMqx09
+ouirVW5VRlbVQVhviPTunwUofKZm18zyfpOEJ4e6+26Adsy45D1ziji8ZCbxchFd
+quUPdu/VaxA957qAuveG0R8YQEG+MNvj6V+C3zzIvoiJo2t0V75KNZ033AKn16in
+eGErY2ad84SLYxin3GSJAlEEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AW
+IQQLDTVq0FnH2jOBvhGgXu9whtcyQgUCWx9paQUJA2ZpXQAKCRCgXu9whtcyQuiD
+EACO0apBWPXR2AguczFY/pNpVEq7OyqP13uZbNcT1c48CBcLGy197MDBdfIwIPWF
+dTrGUq7g9YTnhwAsjpi07ito57B/VY+lYEPOFqM0taY5k9sCubsJsnukpiHap+lX
+YfZ4VrZUfcOIzAGb/rxevyhJq1S8ddmRy7dXUvRnYgOwqB71wbL3yd1tazO0sz1f
+aCHqtXZfpHrCCwDxePjMLCrTDfe0CGUk7d25Vkf+wIFuxRdMZLEwCuhhiLeJg0Hw
+Vn452MtiRLr+CqVzDLbA8jUDoygWheoIjzEOZCE13nBEPD59me/DKib1b6Q72ZXJ
+HLEs7aOi7Rf6lCPIxivHK520BSjVjeejBlTikODgDmo7QiAv5GGa/uJEgBN/Bslv
+RlRIQzmO1oKeD4EjruZtm1jomTQO9137raK5xeUJS1r4UyUhm6Eoz6gbVfEEhdrZ
+gdL/E37Q6zDcRt+qXG/IfRblggLKiHTUE47HZNxuGxvNf4LlFQcmlX+gH4b8oDAw
+4qJMPZlYzKY/3z7grLXukISQa2YdLEdDRuWiQUzWFsK3Baord4VezOnooEXqX9ut
+uzksTYU7Uud+ZSZ5gW7apF+a5DLaY9LKsFxKhtc0wD4yRGW6ieCv68AN3+eJ/ehI
+IdmsVOQD29UZYvcdpuUe+AkE69ttDZPy83jW9rgYdfho1IkCUQQTAQoAOwIbAwgL
+CQgHDQwLCgUVCgkICwIeAQIXgBYhBAsNNWrQWcfaM4G+EaBe73CG1zJCBQJbYVZ7
+BQkCuwh2AAoJEKBe73CG1zJCxOsP/iEgsz2i4zTnn/Zr90WotWXSG8gNsaNq+F7U
+aIWwTYBrRtIFnHPBePmVfNX1AQCTKdvHARXMLNaT49t5PeoLwPMhj1LN+80VYfVZ
+ZLN2mRxHQRfd6gE/oisGGc5jnYIatwFIJ5RKq/sRCU9r2yp3rOceLwBdDw/BcPnE
+dIBfN43rWmBNVI0HeUZyAhO+FABSQpnYiOuEpvYAoYLJroWvTXPT7nKpoAp6pTg+
+61MBzv4ZLkw3B0d7Dk3bKUPLnTeljv4CbtA8F8WZj9T69nk7az3gwnXITO+/taOM
+UCYz10WJNVNJl2qdwdUTNOv/+PvVjZ5iUjy5zGEiMUgurFvodSKITVvrzmmAb0nr
+QgzzQFk2B0sFyYoPo47edz/CN9tC2+WB+VTX+RBgNR+fuQYR1VZX0c1GVNTbjqii
+JJC+t86PSzl5DD6GcjbuB/PswLwQ9im3zhBwtf0rg7SAAIl8FfKc6xAbbeQJsbQ5
+yW2ABIRCW0ZzgrP8+kQV9zsFK84mjLX1w8R0QVAgr+RvY6i0R1r7Ih91Vz3f7MIm
+WMKR2TPaaBNzVkgZChilJUzQKmgB8SxNGMRvSQ3VQkzsef7xwoPiL/vsK5/EB2fC
+c9amkFjQIDYDG8o2uIRLCmUP5bjtigLD7WDNsancD+qfrK2IIK2XBnbp0GPSiLii
+i9MdSQiBiQJRBBMBCgA7AhsDCAsJCAcNDAsKBRUKCQgLAh4BAheAFiEECw01atBZ
+x9ozgb4RoF7vcIbXMkIFAlwtcIIFCQOHInkACgkQoF7vcIbXMkJWog/+P8x/xr9Q
+ZqV4uHQZf6OJs+PmxppmkiyOi5TU10VfKMB3WX+SsZa4S3NL8LAnfwaRN2IfdZul
+NLh0LIE0HQAUoU+2iWtb0Obna9xXHyydkVAMKtMqqlbCKRjv//ElVoouFALnM6nA
+MmlfMKrE21pkXin/umkPI7OnmHOzc/I201sV3Wp1Ql/G8RROeu0nJayFSGz9Basx
+leby24k9N0y3B7Mk/ft9xUVGnHVrycxDFXVmxff2wF49bVXE98eUlyqQziXMUtJf
+U6h7bWoSCmQkl4r2sR8Lz0Zmq2JiZhI7KYbPxfr4spGhIgijD+7ht6fUK4mdPnyq
+WEO/mcQKMLo5CYCFvIXDCM2PJx/3EAucDUrlPR901zq10vY/wtEzKFOtpmfNts8/
+8JtNHTBsERvdXhXwBg0GJ4ZTB8dFPr4oQeX53kTw33FTgGy6RWSU8j6ruc+GG9bA
+NbJSGvJPWXhC2fkxxTO8et79RmX8RIP21xhEtnEXWmYIfmcxRmZDjMquYIQBioUd
+1Ec956GyjAmZIdoBqLwPX+NKpN3d7ZaqU+g+MPLCfzSc1pEpDNF26oJWmD9N2kSe
+xzoDLa9PTEKTSMlMXuiIlEgVTCsrgGo/GilSzEyoS7vPLsGPySC6CZ548HnUz2N5
+ddRsrpJF2iHTiHFRItjcDYmA+FF3pd0MKLyJAlEEEwEKADsCGwMICwkIBw0MCwoF
+FQoJCAsCHgECF4AWIQQLDTVq0FnH2jOBvhGgXu9whtcyQgUCXJRzQQUJA+4lJAAK
+CRCgXu9whtcyQlaDD/9DlY/Zm/5uHsFjpDcrVdLMJizfRGKTm4mMPyK6inamF4h9
+0zFJhSRCKMJSKV5fvrJ54CdRc835wtMzG3DxU6YioHAuyPwcJv6rut3OPqDuH/0i
+rSL8bwB+GjAtlTO2xT48pm1IFlzdiGOQNhFAzwxX7jFJbyYkbwT8w1h8EnBrLUwR
+/7rlTXXTZtjga43gjPX1RSA9RuUGB4uUNVT+hmBt/W+hpCVcV4f6eV3E1UoWHAEH
+6bEKyeo4Rv4DGoM5UEYzCL3QVKvEfuo0GQcBprsNCo+fQPHpNLf+ZAvqARZVzpsG
+q9OiS0PMjDL6nLf1H8ehMQek2EUAS4nRJBH49AKJvz+MThI8peNqNpttnMXu2zO3
+kdV5VagHdyFsUmmz9QdptBU//VMIrasxw/drOVyTEY+XFeSW23KoQrIuSPoXuH/4
+J/M/AlZlZNa4KMOqdVddl19cp2DGPr9lYsnG2XwzNqlJ3WyiWJhRIeRUf8Sr1s01
+cigOHuh4eWWUMBpWG5i1ZgvCbXhLAMEjsk+NAykY1hmLH4XblEk+1TtFTKWxZ9B5
+MuA6ZoSfwnWyCmWKFZG7q6la2zIhxhBu8CrnretNZIpYgW/6dvYW19i+9qLI7xH7
+SYWoLavlbZ2QLWjbOBRFB5Hp4+xw1i3ehr2Q8NPvH8vaqXBq8Upcl4sHa0Vn87Q1
+Q2hyaXN0b3BoZXIgSmFtZXMgSGFsc2UgUm9nZXJzIDxjaHJpc0Bjb29wZXJ0ZWFt
+Lm5ldD6JAhwEEAEKAAYFAlmo+VQACgkQGLP1D+WSPBjcSRAAxuqbAvHu3MhkRevx
+aPfC4kEk2yY0i4JzEk2Wr8KKqvkdKTwcM1B7X6lzh4ZX9QakOgpzlRIgFj16jGyU
+3eOFhRvdNp+lgB6ZgK4+kAGckM/UiCgQfmDL2yFgkUKlnrMoUgiSa8ZmqsF6waZ3
+NFqOlRMCGRDs8bPcOyv7mEcuqE/nVmQ8j8Q29f1DcnVe/nlBInO12TV0Ofbhoz/z
+vV1ClYyMm+JDUrQE1ozDf6aoXhwJfJHNJCJxII9GQUxT/ke76rt8DgpLfK0xGw3I
+qwCqiYyyp8N9F5n4odkvNED3X/uoJb6gs+YLIKOIBBvLEoGIlm76yOBpyNEYpdLZ
+YFXYk+mEA069AdHsYoQGB3n9kFJMyDeyRct2TEurXiMWsRV5zOmpaMta1xNnyAg8
+XfN0FHO6jEj0HSmcZ+MUxeed8nMk3YasJyp9mhppog4WnomUYLlZ41rCaVbPgJ8j
+0MR360nMHKZKyEVImUHtsLuCyGk+kHuxh3o7j9Wr2nVGYjrmKAEWKomlvFsEtUW9
+ZnvXAjhQesLefJAXKovmuk1lRFPWuOEVcs74372zSP0sQPQKg01CKiPDyALTQIhE
+4zKHZPa6N7HK5DbDzAIXS1RFePLqbQNJ25bBxhBmvWq7Tu3frmJHNQA8GuzDC1Rc
+XBQQUigpnoRN35RbDUj9u3jX7qWJAj0EEwEKACcCGwMFCQGLggAICwkIBw0MCwoF
+FQoJCAsCHgECF4AFAlmTo9cCGQEACgkQoF7vcIbXMkL6YxAAhTUZMMa5nPKC2IYN
+c9AS2jhkssKfUMslM/hn5R8Iw20mlfLuN186UzAR7zr6INWyQ44aXZehLdDU41o2
+xXZR0IZ08Ys0cG1xIvxwsyr2jKOCTwDb3K499tGZa7rTlvhYm5GwOWGs/onF1JZt
+2asn5yYPOtmb66bwoPkFRyiMP3feI/o47eus2rVU9dMQ82YJ58CBCVvxq0BQAZdq
+NzhmxYN0QK90zgn0IiAu4Och1iFEW1VnvfdHO79y7xi7TiEXilBr/I5nMsqdaNrT
+8ErtGHTZ95yAClhWcwUEVCrMJaikfnImGdfQA0vHIc8ke9iRJfbWZKLZQBDtNCo7
+MiDd5tyyVrGfQBjN6Vy+CHIHDCYEpJP2WlUl5QO0LMyAJNqBOF4JRAHC9HH3rqDt
+4mUy0OIm/XzBMMSFAVPb9uGhvN5im1AYxx7ilyatLO/NPFGGy/sNTn5+joZZIJmK
+QjhIL8tOrZYrpn2jH0FSCG/ftM/phRSaHowBn8RqZs5n0JXfZahbj/wxjCFF2KH8
+0yOnYXn5mUZsvX6Y37T2DXokLcQVO3l6/sgO5qV4jOjJbsRm5t22O7KBIgIX4PTa
+4Gsl0WeDz2ysujcLaIZvXWgpYDVMMCNAu8H5UhDFs1EUc2stNogP3TbwYgdvFnyg
+drjvcIREs0IA+1H1SaXPtLeM2PSJAlQEEwEKAD4CGwMICwkIBw0MCwoFFQoJCAsC
+HgECF4ACGQEWIQQLDTVq0FnH2jOBvhGgXu9whtcyQgUCWx9pXAUJA2ZpXQAKCRCg
+Xu9whtcyQrAGD/4itne5M74b0J3FfwLuRdJuK+LkL90zFLJy3JkVw6Xcmk45/RwY
+KsG3b2RlqzuX+ETgeF9Ov0Wk7L5I/LrRGR3kT1U6MalexVxrG8M5/34sVRqKNanO
+AC6Pecduh+O7SH+FBDR68/qJTYAgjnJ7sYNENJwHTZxFa8FuRbFOum5p4kmAZrCF
+BvjCQFUPv35rjSe9ZnUOaMY9lEIr/Q5cDUHm4AVIfOfeUjb09FXxmUi3K46smj2Y
+IRTHya5zUr8cJDney9pafJrNi+RUkwG4qDTRpVKLD1k3BlItZ/gcN8wvP+/TM3Gx
+g9B8XjxntTvFU4qRQfDclZtTRXHg2ceBTg0G2CvTF3FD3YYOtljhg2SeObjGxiF7
+6fTphzIPmSbHocXx4O5up1TVx63QFBvx5nQ09jCXYsAXAR2QmDUal5xRp5TOJgCn
++0KmmYzcmz3FEGJwWPq3D+23q3x6LN8JKWHQbpA04+K5QXRwN53vC+hYHru4tFhO
+bG0bt5EsoYPTDI6rB53ZJbikyIcqyvMGnmA1A6OfdMiMxgPfvlf+4VP1GU+NYFKm
+Seju2bfEM8L4xFLPUMUr/7BQhV2YCYsUd2FZ2yYjfzrgO6iAJ5eQtjv4suTg5WnD
+olsc/TC+9BAJIDFfOgm8YqHB1c2N7eIDOH9KayMOx3B1l+67/kD4aOJzbYkCVAQT
+AQoAPgIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgAIZARYhBAsNNWrQWcfaM4G+EaBe
+73CG1zJCBQJbYVZ1BQkCuwh2AAoJEKBe73CG1zJCbmMQAI9qmpmQ3F6on2X82Bej
+VjixhCAr/gY7+MB9b9up4S8QdrJ1l8UE+BQdbFoHdQoE9iVxxKBwC888TK25clYs
+111aw9YI3bWEcLbylqa+8aHSrj0g3BL84z2pwZzGO7bm9zhjMOW0311h71qYa5x8
+vi0Ukkkxb3vHo5EvT6gBu1yBGy1nHtHEEHfoxy3SdSxiCb4hJ02Fpn9AQksJ9hCN
+yz7Yvj5/g76dFINOiCDaT3gQ7IHqa9Rquq7M7smeZ92W31oEhvcTuw5nmHEmx3sx
+pLHPa3vpgDK1UHdco9bhBiO9YmhGfxa1tEKPb306e55WZZSdMT8e4b03hpk39mdJ
+5aL80cKk3ZxFia6yEU8W664wprI4ysVgT6Y0CGv53P9iRo/7F4/foNVY+Lhk4mCt
+/k2hyW7MrxH/JVT76Di1RUsDvEAgHB/NHRDkbVJFHhxrMdd8Yy8S1WLlqTjtPrdm
+IHI5sIzkxmv4AesdA/2CRyBWWxLyBKRvhXxICholEkM/aj+HKmIKGwSVjqxbV7Ud
+lpRH39+t1ZuBWvT4sW4pDkebfN6/pyeWRprcjCWl/oF72bLkLXH+yeHnYf7gai9m
+o6+7XEiS4QYKlNad3X5pypuz14KdpdhzE9vnDqGjQbZ8b04ZMZYEX89T8AKDAdNV
+geXKe5KXsw3e4+1amP+kgQICiQJUBBMBCgA+AhsDCAsJCAcNDAsKBRUKCQgLAh4B
+AheAAhkBFiEECw01atBZx9ozgb4RoF7vcIbXMkIFAlwtcHgFCQOHInkACgkQoF7v
+cIbXMkImvA/8CCW63v4QV/ZBDR2yWmrTM7EiN2eiBOl9THHWLQrzoDVnVaWQSjZf
+w/irEyy5QkyqnlD3BxhVBCF5v+KvB3Ydk+iZ2z5zVR/sJAUy6b18AX8pErJZzCM7
+33AEVCTeEuQRIORZG0haGHsIyG5k/wmm6FHYapYS23koMFb9E3Fx5oHS+kQ7+4Lw
+9MQfUfV3bC+4Aq99fU1XYIZFSbp1Veu+0e+tIlAKSWVQzx2dyiBoQTj96AEu1QUe
+cXDZ5faO9O/HMhBVMDVJQDMwrPG5uYI9E4Vv3B3r3hThIFPpJi4YVqPfiNt3xpok
+lV9K+Nkw+Ti1GvLNgwV9pcq49B3QB8BTXF2XDUsnD5l0xsKpg8DAmyHMGavQoMsF
+o33RgMkVezhZsYrsh0hHXqqYUY8idKctsmtLHVvgPeYXIyDP2wMdDbaOF6zqk6hv
+yM8JBfTArXxpI6deuQdNrPsCqW/uq/HmyPHQKuu05GnODpv+RlZvCcCf5o7bKc4W
+nJxZlZeq7fYAbXqJEv7r6dV5wq4OrBlJsZILuCrdyxOmiiO1EDHCOJdqNkAAAvZu
+t3fqHE5Frl5RBPWl/163AwP635MjbwNQpmSpuu6pVsjGTDeonXzWDlh+tdjhrz7O
+vRhkcHgwsYov+wF2Fj8sZDQuniMTmvY5Hz71YlDcNuyvhVqc80l5tpyJAlQEEwEK
+AD4CGwMICwkIBw0MCwoFFQoJCAsCHgECF4ACGQEWIQQLDTVq0FnH2jOBvhGgXu9w
+htcyQgUCXJRzIwUJA+4lJAAKCRCgXu9whtcyQh+LD/0XbhrHnaRSC8ZVAG8fmn77
+Kb0o7fCPwW/CqMEkq26TI2Ze/qAV2J5Iu54BHl35Q8D92OmfqIoKOum5WnPeUKaZ
+MA2gUiOJvnabo+ufFY59dWp2v6KxtfTP0cWtnkdIeAIq1UdhTv7VL7EZ3igunlBY
+NdGxOqBtJDGvRcdiCpWK4PvHVcnk3m7XdZmt1+/S26kLNGMgJ9dwgB+ct4GdpQAI
+aDGEpO9BKSjKlybRvJQBofDt3FKB5ozFseTHqCN36AcHznJDbImP5/pG01FlDi8i
+MMqR2QAKcGBa966NKXEGMHG1KKI2VLN/HePcSw5ooGUV+CAdhYJunaLegfhG2rOJ
+fn1tPsWchYHpUjV1sD92A6ePLLqKTXBm4Zx+65akn/7I5q7xJ14IBBDuy3q6PhYJ
+81bQYysQz347CORFBXS6yBZhUI8UBism/IYqPfBjCE0Eximi6U8UknfWItdszLVC
+P5u7d8j6yFNd60/MxTABkUQ8tZK/N2eePUBQBeLggl4cVr7z0TnfimAce+X14dnO
+xmXfRzgenmG5mnQocAX8hne7KX7Dc0nCVDwZLTg0ZvrRQ2GfazmhWehxYyh4mWf6
+Ku8D39sW+eimrnvOdK8NRBTFsJAdSF+BaH8V5ouV1M+fy/ZNY4Z6Grc9/osJlhFd
+QwRmGLakWbvUUo6v+vQo8rRHQ2hyaXN0b3BoZXIgSmFtZXMgSGFsc2UgUm9nZXJz
+IDxjaHJpc3RvcGhlci5oYWxzZS5yb2dlcnNAY2Fub25pY2FsLmNvbT6JAhwEEAEK
+AAYFAlmo+VQACgkQGLP1D+WSPBjdFBAAo3cJg0YIWU5DTZ65zpDSehSFo8ND2Paq
+PAwUO4LAVk84uoHD6y9AjklyG999wVnjSs7cq07jvJr5A2zyq1lTr7cAcIXvZLVz
+t7Jde7VukTORomvhVgfs2O5MQbsw/VS4k5EVu+Z95z9BBrsOfcTqhMpkplPcMZ5P
+EvVBlWJ/XrxoJMroX0QVL5fADbQ25KgbARMvi/rFELtEFpjfsAh/gyR+Ki6cje+N
+wisqulplwX9/j0g0ylm+W2py7pPMCKYWTtsSMzeToswwKJMc0BpK693+gelLbK4s
+sB2DEAgLdeIi4UF+L8+gsIUodqdRH89J7vV4C1xkfFLV0r6aacuZiJFUyeCP3K89
+Pw5P+4rGTmwlDUlqzvQK6X/cEwYqiRnFKV1JiYEnrBBceWHJsFuErlvQ+qUX8vcR
+VnTF5zu0MzYRS1WDVfrjjHTrfTdPUdSJjIPzL2AfIx4npFARHIj4R09ZJcVwcvou
+Rg1ISqUySfPDbt7ol0EokxlTmWW9VF574P31WaABu+WL7c2aSljMS2AwtLOvr+KF
+fFyoIZJZnz1XGrzSuwp1Fun48ZhDw9IqFvhyUEgWFSPFw+8DturB3NBu0Za0/lrr
+NzbFIYxzyvwQ3KFDBTcSafgcFodw03pvJAK0VgPm3acHDSVHj51F3eMH1RlMAxyz
+dSfE2Yh7OryJAjoEEwEKACQFAlmToCMCGwMFCQGLggAICwkIBw0MCwoFFQoJCAsC
+HgECF4AACgkQoF7vcIbXMkLvgQ//W70n4+9dtmD2+Q0e0E3fqGVIndCSojVHQjJI
+uytRCAxuS1UCGAkO6/kT+0iIMSfw+NuzkeWjf+mvezhQSG5OllFYsSONVOmN7mSB
+2oUxNN/oZ6Cma/eWwOABtwIMWCcJaXQ6iBryhf1zRFrFpCVqheyq+dEKRJmCO5Ty
+cb22LEeEBiVa2q+jgjW7vA3ds+oUXEcqxEclFofXbktbfHjjsDNg27s0ZGh9/Yj2
++OsgF9R/oejnJgBUoXESW1OkRvG6mI+6WRJIjjMHhvOAAcvvMGADzIniVjialXaL
+sWZiceJ5apuv19kKLdmBd5CRFRdxFdfDP0Dkc51Y+DkEXt+xiUHkDJQRoseuijiP
+6c4CGmBwA0Tmz6xCXuFpoEfuxRYXFfu2m0DiQXunpjIXQ76uVqe3PMKtm2mQFaNl
+NaNTvbC1UXIUCC420gBLOExUSjQ+SJ5L6bM2gwciwJhG+EfexgfVefkIqbtO8ZQJ
+lfIQQJiAJwzAmQqwSrEkPz1f6uTFq7aogK7hCZD6WcamGezqeb0dp1jdL28d3BbS
+Fze+jbNgrMD2LLrdoOitdEUFicaYfFc1K6IS6HYpviMTHVgt8KHllKsprKS63M3Y
+rgFHgm8xxGVKfe9iWa6dtM9b3PZ3XJ77uFHje0xkpEcqzkj2s9O2xM1URKSqRV9F
+OHGBUtiJAlEEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQQLDTVq0FnH
+2jOBvhGgXu9whtcyQgUCWx9paQUJA2ZpXQAKCRCgXu9whtcyQluIEACGndwTsQSD
+YvTG+ibyToIOjDm1XbjiORc7vhqCImg0l1esaEKf1nPOch1KqK7rTTh+qcjsgzXc
+AMhxzQCvbHwpQFycmZSjSlWSk3C19uAMXyEM66s4yBjpVc5MlGRgbbAYB0ooiOm9
+eRgjgrqEtrQMWrDkk1/8n6pyyCmkrWVh8Mp1v8NccPAoWsyAzJNhWqaxxMvRxgjH
+GwdfbdRVWCtgv6oeZq0eypLL9rqir2KzKZxillgdeOlw69THa++/Ejpq1CEYdPt/
+VqTB1pLQm8u8eHg6wz44AN+Lr1WShX+TR3aVaiIhbbE0YwlDI9sGBgnanN/i9CaM
+WYMjOUucj3czHd2jVBMksQ6Rkz3OSbrFroKg/jNUNqbWh+HY43UjDIdiq06+gFIq
+Tu/xM1JRUwN1BXg3Xml/fY160JbruzvvN6hEzmBFa9Xhq0Airk/032A+Qsw04yh9
+YaIJuajrNk82ee/f6gSlzcCAmSZIdTlF85lg0/kpRJTbtiWNyP9HeHSyR7h20JVm
+Xys2k8SrYI36Bzo92bbryRNmHB4t28VthqsH9l8OvjGY/jRogQsVW9Qx8YnCM/bZ
+vgaAHNdmjO2N3TM5jY5+PkxiDbrCPr1/T/hr4znfPXgolLFkZrCqY1HCVHXlVZw9
+WaQeONDxc+rEmsx/EMZs5GHq/X4hKGBcb4kCUQQTAQoAOwIbAwgLCQgHDQwLCgUV
+CgkICwIeAQIXgBYhBAsNNWrQWcfaM4G+EaBe73CG1zJCBQJbYVZ7BQkCuwh2AAoJ
+EKBe73CG1zJCFXgQAJWZLYyQikx54xeU7OcHdUzcc6nmoaseOMTpd3I9Mt3d0CBn
+8JQOEDCTQCz4qHRthsdXgz6pxjjl59K3tv6qkCd/un7RZIW1xfGKgHL7yWv1EKiP
+flheszCD0uRBSoVR3qfBNruD9yy2+NrzDyzajo9N8jPnCdP45VY/V+3UhyXpUJhG
+18ryv2XPqRlUIFw2QVIwc8ff3i+Tk7zF/Pf896sS66vf7JI4Q5NNgNygEUXsCj0q
+DVKpv8/2XdTEc68FWTbdwDLNoXkBRkFhvLH1XVnq7JiUouWn0ELk/Da1IvbJwoZ1
+MrlzkSpspcwhmChMHAJcrS6dxMjiyo9M8fbfPfKTQOvr9P4M00i9EWHKgILJEDC2
+Gaa8IHihhgbGSdas71jRJWytr1EjOmT82pGzYOUE9OR8pjbj2o+14J8AQGAKHGla
+yrSLi21RxrO3ozdOLAS8JFdveOTAYTfViSijbxRcufh0GMFPmA39CqsDNSM7SkkC
+gHeiKztR2S5VxKCnAixy2kYxAp1gRMgYFvFECrEP+5x5LQtis36Xv9uh3km4ee/H
+ZKEPisLku5c2XGNJsxscl68c9Um4Ike5zgY4D59rowg219RxrJvJkf7EgRuhhJQx
+IPdrOLqK1/qu+RH7n/YOO3lyH918LbPzVWeNlYGnJzg2wrWgTg8YCwMwfJOriQJR
+BBMBCgA7AhsDCAsJCAcNDAsKBRUKCQgLAh4BAheAFiEECw01atBZx9ozgb4RoF7v
+cIbXMkIFAlwtcIIFCQOHInkACgkQoF7vcIbXMkKS6A//UjHAFAIqFi7C3/AlgA8z
+GPpCoBeC8hTOGV0c7IGkkhjK0+4gfW9Qj6wMMew/yM4+wSbqtAhATEPLDg9k6LFr
+Xn0jTq/mU+zFTLFq+OHk1cmteqgm+uNVS9F/u6gfPKw5KPEsjihuP0JFSQ4o14rO
+qyvL4McLEeZW79pJKsUbqKgWGFKJKN7i739HM2MsvLlhhLw572XOdztH0KYy5Blg
+qJECerbRd7NwzlcavckW5x7OS9TSwYivqh37mRA9DbMBcOHyDA+ptsntJKBSOD1E
+mrSnM8/RSlIXaCinBpdd2D/MkK+44yu/l1XcWq5kt+oQ2pykYJIu3g50LlmCI+3s
+DKXT6X+qy4i0SdVP0sggYe+Wij8X8pUqwnje9s8RykDAuQWpQNbm7xo547aGz+/1
+Q7ghZydw0qlEtLbageJ2BdK1DmDvJZIHHNkxgheEc7y5pjf9d1Xww1pZ1l8rCHNu
+m0mgBZoL3Pd2yAqk5Ec1raG0VJiL/UcO456Qsftko3JhyH5IRYlM/blssnd8eIl0
+3509MUMHCJMG2GZDriEc8Qo2f4jqlW72DMeKX+cYBJQcFszu/HGP5ROi8/YIYXym
+HYmiWiZc8sgQielHim93ZiIpt28qywBNnGUmYBvr6e2tNAIbIZqExFfGmC9JW6/+
+WsoaHfh20D3IGrCKZdIqobeJAlEEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgEC
+F4AWIQQLDTVq0FnH2jOBvhGgXu9whtcyQgUCXJRzQQUJA+4lJAAKCRCgXu9whtcy
+QpwLD/wMnETj7elIrz4dgsiymRXF09yEgegI4D8xqm3IbXhx3FaVcOJCjuFADWlw
+HUAIQBb9n2oVHEK1WkjBlEpTP15DqQe04ZjX+f8PCaPxooNn3WZLXUR2ilEwTvbg
+lYT/7y1BLFjW9K0ecaxFTRzja38v3OzNy5wGD0GePbuFDuzVhYRX55wDMgobNq8C
+wa0b4F2skyHBzr9IAkB6RZuxN2Ab0dIobYsQZsVOSxAyXzEdt1J9V0rd+TnOMmeZ
+VzwuiVTKZvEZuUj8wWlpAffHoSjuzHP2Pa/rlmGrFlZ2A5Bm33Phm822lLLfQwGl
+Eli8N6bBGki8EZhe35PCy+yIJI4DWaXS6w7r95YcKoGWuLgzVSYuHEC+9LIs+qat
+lojMWSBGl7NTyXHUJ4J2lh1gJSqEzvWvTdD8iCky4WLXmrvHC9Y1S8AgJcqN2jEL
+OaUYCw59VhuHqKKirx8XOhqHCQMgkoy9n+0PtzgVkAxND6NtfjeqFTUJHT7YrrZu
+XZyhWe7gUtrhzUXaIXh3gAyeaCMH7AnrDOv35hLjq4jv/8nTIV/q7ubSVfQIN2oV
+MIGlbFaxRKKSvMu1c9QhMPzPcu9dKe9P40DxhZ/Mko2EsEgrMm2M0g6dajeufQy6
+RX7EFbVrKmq3wV3Z0o3WwP0BsoRl+8FWqxCDSMY/SlbZ/ymN6bkCDQRZk6PlARAA
+0wwhX7Rq3xrEaZ2+LmgB8iwgBXOFGZ5/cTIC25gRB8MahJj2JmCuTHaV1Q8p3Dma
++hu46QziTm8IjN52ITJPF+Q7RkrGscIaAEZJ8yko7wT7nC7V7x2poDYQJZNoB3tY
+kPfyrI/WT7w641LskUiQ1JkMK0e2iASwTZoiLHADN2e/jHusWafDBDqd3ZYEFXfo
+zV6iw3ciF2u5ZiDQ8jU8iTrKZA9PvHcr4ifmqhE0ZT5u+ytXaRKH1l18U1dvSHCN
++ryt/sr3Ow1O12XxqjRU8WTOTaSrEoDpEmheuUGfupJXFE+G1pRc9RGtzChTobzo
+tyReRb5ZjgnESdakQHsJXOTE4pJLfpeBawnJOk/jbvR9v8NOSK5MUirgDkG16EHa
+o62MtND9fnVVZXDQ8xbASLLDvs7Svhu+AgvgIKjkb/DNq8PhJboAkfwfn+JiMv8i
+ckDCwNQn/kZdpOEZsZ7/xjJsddud5LMy6FdYSA3GZuNNyQI035GJT3IDRhR94DyM
+0qzq5mBqGrWYxrRPP5Yzvvc/0nTiNs8G4Tb+0PvodETFWcgGZEakKOdYOfd+nwA8
+8SmihaM0g+tReCUGm0fZCI6hZkirhJC35FQG4GOe1zvtV1WeinkpF66WNZzNr/D+
+2EUBEryHTLI4pqMH8o15V40R6U3YyAE0YCqey5iU770AEQEAAYkEWwQYAQoAJgIb
+AhYhBAsNNWrQWcfaM4G+EaBe73CG1zJCBQJbYVeQBQkCuwGrAinBXSAEGQEKAAYF
+AlmTo+UACgkQ1NtKn10ETxqZVg/+JcV6XanyDRdzZmNHkO7BS6DqL+hv+bKce794
+tzzNpJ9WIZPqa/t8YcL22z7UNT1FxCYVsgYCJ47n2LIlVBSc0gVc+yqPgNad9w/5
+PzHbC3vW5HtQzAqDdcVIDa595qDk4J9cuSFFu165bNs+8eEgHagcOVeZ/KufALjw
+srKHcEGm8M+A8aQwtvDHCfmxDJ8OwgyfuvTQYKCn/xTLR3p98apOC/Jf91he11io
+AHwrJ9LJsWw4IwCVFy+/vKNvpTj+vGcoro5JhjmV/HCRTYRMO9Jfdam/ZtsXK53M
+ea28oUJiphFTRBlTEjt8g6BonLNdBuL/Z8jBtrfopaq4lfEQynOAZb8koDnOZWfR
+9UiIL0lXM5CqpZYOiRG4sMa1P0kDH2s+oKbMs0DfuAl0DdpjUrUEKGuu0K82+u0p
+q3v9J8B4rzIV3ZNLS6Z99+jCbIBrQiaHNffIWLP/9Hqd6R7fkfP9dj38pvfH4YvJ
+yqz1oqtYB1dyQquxaOr0plO0DO+E8lVxDqCrEkztk0idSsAib/7iXQVFNuxZuVFi
+bNBWS4ndq3mGO4iYP5QT0oP9oJD/cCGHmDSvS78GZ25M8Nsww2/sXl2Ph6a6+2kU
+dQxQoRJF+J2M4eeoiE86L3ZrpQDjkexqaBC7PvuXplL3QhxaDTKr5REdyll/Ppjd
+Ni3GLMcJEKBe73CG1zJCeLsP/iZBv60aJB+AKQEijm4AkA2yrGOcJr9/hVbSxS4/
+aW3jXI3Js3+44nwsMYTYHG2ZUhwikG0whnfy9ZvIzOPaKh6IZM+qE+okAMnlu50a
+QU5BaH/lFVRiGmz0G/1+S/EWwU8jlqlQBVOVXRkxhu5EB7ZoKJ5U11XOLkB+fPxS
+yVGq22PTLJ+FBcCXP9kY/X5bMSUq39txfxwqGkdSiAhK4Vp2nz/ZJyNNm/IyK/AL
+Dci/zJdF4OXZ60d048dSamUN56/EtIM0BhaNwBove4gpsnIOjCHmQ5Y4l+5a38ge
+WxL8sWmUP5Zm0HU8XLyKBiTKDLJc5tGDHSGQuKmimXJ+MMtzpmU160pf9lO1XqJO
+riIAYmqRCuLap5b/mfC7cRLRtr4D6O21wkft8EI6B9T8WzgpDjwnwN1aufrWFvZV
+4KXPG5splUwGKHhm27EWQ/F+jhARliSpZBxnEtcqz4jPLNs6hAM4/tsUR8NLDeOQ
++i2Q8iIz8EMb5tYfGeNFGOU/Bp98hMEvL7FPPBNiLgVWF+3XWnwPp1LrtrHSGrRc
+Xg0MY/w+3OJtXfe9p+irFIFOnMyYKLYJ0N91Nk3t0a7qvzsyq/1opO+IAoi1Ioea
+0WVRIvpcGh5beEV5aRtGFZuUDAie0rOcQHbZDMuFv9uTrFpyGGR0K7FA1+jN3aXq
+GtRAiQRbBBgBCgAmAhsCFiEECw01atBZx9ozgb4RoF7vcIbXMkIFAlyUc04FCQPu
+HWkCKcFdIAQZAQoABgUCWZOj5QAKCRDU20qfXQRPGplWD/4lxXpdqfINF3NmY0eQ
+7sFLoOov6G/5spx7v3i3PM2kn1Yhk+pr+3xhwvbbPtQ1PUXEJhWyBgInjufYsiVU
+FJzSBVz7Ko+A1p33D/k/MdsLe9bke1DMCoN1xUgNrn3moOTgn1y5IUW7Xrls2z7x
+4SAdqBw5V5n8q58AuPCysodwQabwz4DxpDC28McJ+bEMnw7CDJ+69NBgoKf/FMtH
+en3xqk4L8l/3WF7XWKgAfCsn0smxbDgjAJUXL7+8o2+lOP68ZyiujkmGOZX8cJFN
+hEw70l91qb9m2xcrncx5rbyhQmKmEVNEGVMSO3yDoGics10G4v9nyMG2t+ilqriV
+8RDKc4BlvySgOc5lZ9H1SIgvSVczkKqllg6JEbiwxrU/SQMfaz6gpsyzQN+4CXQN
+2mNStQQoa67Qrzb67Smre/0nwHivMhXdk0tLpn336MJsgGtCJoc198hYs//0ep3p
+Ht+R8/12Pfym98fhi8nKrPWiq1gHV3JCq7Fo6vSmU7QM74TyVXEOoKsSTO2TSJ1K
+wCJv/uJdBUU27Fm5UWJs0FZLid2reYY7iJg/lBPSg/2gkP9wIYeYNK9LvwZnbkzw
+2zDDb+xeXY+Hprr7aRR1DFChEkX4nYzh56iITzovdmulAOOR7GpoELs++5emUvdC
+HFoNMqvlER3KWX8+mN02LcYsxwkQoF7vcIbXMkLCSQ/+P4offtEj5dxqb/fism9S
+sqvW1OPR0xCmF5yEqFF7OPdJZGNeU3VDPl5uhOHYYqSDBo8B8iC9pmANTlQoZOJ4
+KyGiuFIXEK6MTFd1bks4k/87aDKqvFWL/BoRoVs1AGvuCbufkC3NperiXsO6m4TS
+Nmgw7yydyKyqN927Hh0fjoo6OmrcWRwRTmrmt/gPyRJMQagvexsG6amsNN6ya3rL
+8VNZxegQCF2SrpIQsjt57GzdGk7+hhWpTPEny9JdcDM4tF88YTEgS28kYOS3m7Jm
+6x9UhQPIcL8egrr6h3vatlg3Mj7IapUsQIVgzWjnZnAloVVv7ZQOnv04spakNEDf
+Ph/zJ2fujGxhVYrj9Q18YZXVz1zAOC6EPoRUAxL9eb4B3MFmmTMZq0Y45CYZ0S/1
+gtK+D1H1kCuQznGQ0z53YQkFJzTf28ITXtAKW/6A5uz1f197RNx5Y+KD53tWNBla
+nZedNiIkiHybGf2KgBHMUujpVWZVVHgpM5x9wbPuuUrRQ/52iVyUErAtbsdUgQMU
+kzMOiLgjTZAz70WI2qNbx2ttSFFrSTy9udy48blZrQ2ZpGdfnOhiVHCnlYxiKeqM
+S9d3+mGJpqwSvBLwymHgtI0r2AJoO+6mSPNPSrO0U0u0hv3/lOrXg59Z0GR/YzUZ
+UZ6AwcpvNrQOlok+WfYliFC5Ag0EWZOkSgEQAN7JyINQoAd4lKY3+26eiLKanA0u
+BAsTu1XpnFa21verGnISXncrvSlvTHveGrPrHhu+mq1jZftfCpIBfRTg8xyz3OG0
+wnnCGFosvK3fZsfOr+ORCcQFxVVzF9cueXCIqatpFdvHA8FUuN03MJ1nrMGFz2d9
+XI5yUDzVNHNjaIYM3w0RPg4q1vg4xL+KKWW6Fs1cSvtN8nCjHgZTJ41E2udyHS29
+LL6cIaho49qIIdfwScXpMpghlIHxe55M8ARF92BsOUpBUr2J4nRh0U26Opy4CXmE
+wAoylwj5MXixAfdSMUl8a3lDXx4/RHTo9PCSyCDygBe/dYS4EygdVvmeIit1anai
+muS8iPfe1VpbdNQPcRQkfL2fYkd0NYNMFx998fBdVPSJH/wq6d6JTCv5VLmyHprU
+ON9cVpMLFXMkxqhmL/KreczhiTv8oQuk+RiRLWOkN+sfAOhcLHymIA7gufBt8Iw+
+ggUAJOxWoA6QUAsRwor7nCi1EPENKYkEH0dosJZl3NDxjbhhH4tUqznQ+EOGg0dW
+2EWtZBPlocjLiMhE7EwKoHY2u/Y7w9Hh/8Bnj/fWXUyhIaVWhkzYISvroGb2YIne
+BwJn1cCacofCegBoXF2PeMgDNSPkD5OFzcxvgtNmOqtSZPtfo174/f/f3SMlfPJF
+AGMnCcs1I0DqMvPfABEBAAGJAjwEGAEKACYCGwwWIQQLDTVq0FnH2jOBvhGgXu9w
+htcyQgUCW2FXkAUJArsBRgAKCRCgXu9whtcyQjBED/48SjmzZKXzJkHUrgOKyIiJ
+r3ELJ2DoHkUlGhWceoftZILEajoynDmJ/uM/d8x19H61i73VQmHVopi/x5mXNJOM
+QccU5zvQdrXRW3Z106ct0tgaMW23zJP3YSQeXNJfu2ou+zQQwp7wt8I+62tLLlX7
+5BzrVVGyagRKu/JOJu0M1w6zNpGvXzYBXJ2WvuVEhDKzfqaWIt4wjAqGqjTmbA7S
+2DWg8BxFxY+pz02V1bx7tjK/Ap11wBJiCH1VMG6+rOPurMXa4Jei879IDUXGGQqd
+7jy68x2Tjsy7DLhPSdZTN4DO8Y4od2MQKFRSTQNuR89yHqM9RU5Vi023qxq5EX2I
+rq0Tl/MM4NwYPLJ4snOWE5WjfJSyAoV81fJQJLYaeO2w1b8JkRC0orD7QUyZNB/T
+9Q8MsNW1qDvwoSDNJw9DmyYcUhea0guLSlhGQjlDsvwVfuOoiAaf837UA8ed6o3v
+KSRh63SUj9DXI8RRct3IajAvSrzmZK8/9JBgCVRVWNQRpJ2bQfremsSA3uLW3GFb
+DFusS2QSx6hh8joVSNR1wV8tXeVKC+1g6zZwSRQWB5do0oN/uzZPxfX9HNh9JcsV
+M0X4dYExSqRa35XkheqWVsmzdXHaiza6A1va3uobMbt/CI2+BxnRu+WIbCUx2na2
+VdS4pue6E5t3F4N0z5Tc64kCPAQYAQoAJgIbDBYhBAsNNWrQWcfaM4G+EaBe73CG
+1zJCBQJclHNZBQkD7h0PAAoJEKBe73CG1zJC0noP/is42EQ7yl3aill/BBqdMr+z
+54+W40afIxEr71k87zMSVjQGl5pg2+b4vq8stYbd4IcelOSatAWaq5XvXkc92yLE
+OdBjO67bDxm2uZQN3JzB24PbLaQm6s45sk2sCX68wJt3ld+uIO2Xs6cO4YsQdjpl
+vuo8HuTk8LHciNfHlQUaYjZJ8yL+mpAt4ER4QCHGpcpaMhFWPaP0UloMnU4LeCaQ
+DRpUdkw6/OZ7el4XQ04HOZIVpflR4qZ8fzUcNKt3PcAfvhv5Fau6p2AHLLiSFJwx
+p4iTzmdRw6F5fArQmSGqFsmpDrQ469ycRqSXg6PNfZkTcVgLLJi5JswqLFCdvJa/
+9F/06l5x6XF8KZDbaie/UkTN69UCvquyZFT0DsS1hxq02m53pMkhXEYzefvsP+/8
+IiZCN8Eu0Pq2zQJqd/LaWNHhyQ+zvKxnYR5ihytzrLpQLDmYTFM3SCeJVBAEbClt
+PJBGFbICUaPmMKVAlAFA0RlWpJEWdtSzifGFGr7VwH3kntTTY5udWK6DQalcWkp/
+W49EfXcMGCBhaHPuWjZyeEkWbcHJyvoRwkFQVfZ32+RXxC+tp8el/aCoYpQYo0uW
+Em70HEep28wJ7Qvv45ICRYXBrSST7IDynTggbRoutWowNQwTjSVTyWk3hKnZZDem
+LwndVGKu0Gcmoj3BlqmguQINBFmTpMEBEAC4g2QIYGwCmfKYhtvM7ZeA9NMk725E
+bocvUvZtbk2kgqT92iYulkOxvkY3Ish+kBYhBkQWNh+U+ilLc6kdd+zsxxctJAiT
+9gSSMM9gz9cCG+ZXcYzqVFC/+7CuJGyHsd5KVPObSWelrRFN4EU80qS41yRADuR0
+2zxYoNkbK1UERYV+exsWNcaLHh4Jl9I7odNHuty0D6+qHSG7AIEZHd3Wo/dkHkt6
+NyuyTy7ZfxEGUmQIyvWAX5ESl6gjrYEaDn4sEcNGx6rbj1qs3kdUg20fZDzRL14v
+F4rXBHTRYHwLBPtxW5RvaqZZY1K1DN4jIOAL5BpMWVAwMDK6+ku3s72eQRziAl4P
+NljXxuBlI4UFtosZ1pVAS7S4aL3/rt/jwac65r3eoG5rc+/5jsyl5X6mKRLlH7Jt
+QAu3QJdhy2XrhL/EpRoEC4XqqExBaDT64uPqRqGuv7UrFGyggTOTBRhu3ylpQflI
+iN528yKorc+KQy8uS3ypBc6xLEI8vVWcb5PwEbplFeXTCaGld8G6q5AggYiTgg51
+T0S7XMY4Cu0nI3b5TIivBSoArVhtYrV9WTRMDRV1CtJfeI7p4F6O9d1uMu8JIeuy
+/bqV2DrjSIEHaU7iBJl+YXnfczgYXBRJpbQaw6VB92vHnXLbenlimYQnB0Tt87mE
+9V5VLsiI6bA6PwARAQABiQI8BBgBCgAmAhsgFiEECw01atBZx9ozgb4RoF7vcIbX
+MkIFAlthV5AFCQK7AM8ACgkQoF7vcIbXMkIoRw/+P1GnVZmshOVDl8qsljv3mz95
+8QjeXZu28kcZEujzQR2eDjoYmTR0HIKX7EXjklQwKPtS9OKvLNeBXA5/qWpg6Eiz
+XcZj++H6PX0B4AAzV6XhRGJfmpHtdVJr6POnBThfebGs7DK9rXPX8GQN15MLxu4W
+wDW79CazJKIEHIPnsfPwpLUntJxIagcAsqHia/xMlGNJea8G0oGv9XlGXNW0oSBv
+HBz5NGkc82nayh/8SnJa4FTHkk3mhGHyfXHOKoI+OoUT5d8Rex7YTNOZWokFYEdq
+wWBLRXnJ2fYot5pRk6lt1wPDLObc1UyfHNcopgPFeEUiwAFS9hYp/CUNjsMtnZkR
++4QaGBXSymEKY1LaFDY20sJKKV1Jf1axH7bxVOXoaZcSTMAbA7tAr9ceTNyFXitV
+yxa6EpdpKXcXswpNT4hBI8Ep8jkTu9CXmmIJN6/QpTkS0ggHcjClEhlSwQmNyjmC
+Zm7PRLn3FSREyLpHvYhyT13Uk4MMHybb3lficDJSOzPyrpfj7JpTVrvAMKrXTD8z
+yhpET3l19+1BCYfnzCiH1Ryo10UU0WFR96aQEvAk25+iGIABOwmSNx5+toUl6h6Z
+muBtyTgkxTEWvgBkc6rYBHa510iwxnoZExhpFkIjy1aRt94Wozm0G8zBwhDVzTSi
+1AUXB4rc1He6dmefjQiJAjwEGAEKACYCGyAWIQQLDTVq0FnH2jOBvhGgXu9whtcy
+QgUCXJRzaAUJA+4cpwAKCRCgXu9whtcyQtEvD/9DbVWLPtFLmd1Kxhe5uZUaiO1O
+hcWX8zrjtJ08JBI/fQbwakVqZeDCXvk+E3zkql8dIMnXXpz2lirIUolzCSiuXrDX
+SUXpBy1PSC0/HLml+/C8g9/Xwh7WiFlGGZv8YuggEP5z3xdXQpR2B9gtKOv7oUwo
+vt/TmXqY0btT1TGGghJ9a/Z5PmUOIub2m+IMNWoqvXggim1OajxnrremPKDvC+XI
+ccbT5kuclAhHJkxMJdEbB3QVGVmGsd26VcC8JqoczAmbZgcz+aTeaLb0hI2hxDVQ
+aYcUs0XM/5s+f7f5qU9Le9oyDm9OJ49tS3vnW/vy8h3YeWU6AidPNwU1q9amcrmd
+5XNXv8TXK1zKj3jdReCjyV5vFGan0Db9fewddNa7lkX8UoFiyWru4FSyHhy0krf0
+W5D9D2M7Gpvu50VdBsjOBGqg1O1gHTNUIghRMSQKYlP7OF0k10AJngyk9+v7snXW
+0B9Semadwpw02YOcVc1G/zj4+vsyeV12dv1Mxq5+pGCMjUAIHNCa+2GWY4Bgg5DI
+Yy0Z6naCgl9lUaT9i0JIJuyZUkK+g9HFGOnEsM0bR/vYSB28HNE8N6W/+HYTbtaH
+l5XY1qpzdwyC9JyQcLY0PFELkdHSvQ/HyUmGjfnNqNWhy7YMn9jlWTiMlf8f3rS7
+cRWQ6eS6adR9VqmKfg==
+=Ot1v
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/debian/watch b/debian/watch
new file mode 100644 (file)
index 0000000..4c1712d
--- /dev/null
@@ -0,0 +1,6 @@
+# Compulsory line, this is a version 4 file
+version=4
+
+version=4
+opts="pgpsigurlmangle=s/$/.asc/" \
+    https://github.com/MirServer/wlcs/releases/latest .*/wlcs-(\d\S+)\.tar\.xz
diff --git a/example/mir_integration.cpp b/example/mir_integration.cpp
new file mode 100644 (file)
index 0000000..5af386d
--- /dev/null
@@ -0,0 +1,909 @@
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include <fcntl.h>
+#include <unordered_map>
+#include <deque>
+
+#include <wayland-client.h>
+#include <wayland-server.h>
+#include <mir/scene/surface_creation_parameters.h>
+#include <mir/input/composite_event_filter.h>
+#include <mir/input/device.h>
+#include <mir/input/input_device_hub.h>
+#include <mir/input/input_device_observer.h>
+#include <mir/input/cursor_listener.h>
+#include <mir/input/seat_observer.h>
+#include <mir/observer_registrar.h>
+#include <mir/executor.h>
+#include <atomic>
+#include <sys/eventfd.h>
+#include <mir/log.h>
+#include "display_server.h"
+#include "pointer.h"
+#include "touch.h"
+
+#include "mutex.h"
+
+#include "mir/fd.h"
+
+#include "mir/server.h"
+#include "mir/options/option.h"
+#include "mir_test_framework/async_server_runner.h"
+#include "mir_test_framework/headless_display_buffer_compositor_factory.h"
+
+#include "mir_test_framework/executable_path.h"
+#include "mir_test_framework/stub_server_platform_factory.h"
+#include "mir_test_framework/fake_input_device.h"
+#include "mir/input/device_capability.h"
+#include "mir/input/input_device_info.h"
+#include "mir/test/doubles/mock_gl.h"
+#include "mir/frontend/session.h"
+#include "mir/scene/session_listener.h"
+#include "mir/scene/surface.h"
+#include "mir/scene/session.h"
+#include "mir/test/signal.h"
+
+namespace mtf = mir_test_framework;
+namespace mi = mir::input;
+namespace mf = mir::frontend;
+using namespace std::chrono_literals;
+
+namespace std
+{
+// std::chrono::nanoseconds doesn't have a standard hash<> implementation?!
+template<>
+struct hash<::std::chrono::nanoseconds>
+{
+    typedef std::chrono::nanoseconds argument_type;
+    typedef size_t result_type;
+    result_type operator()(argument_type const& arg) const noexcept
+    {
+        /*
+         * The underlying type of std::chrono::nanoseconds is guaranteed to be an
+         * integer of at least 50-something bits, which will already have a perfectly
+         * functional std::hash<> implementation…
+         */
+        return std::hash<decltype(arg.count())>{}(arg.count());
+    }
+};
+}
+
+namespace
+{
+auto constexpr a_long_time = 5s;
+
+using ClientFd = int;
+
+class ResourceMapper : public mir::scene::SessionListener
+{
+public:
+    ResourceMapper()
+        : listeners{&this->state}
+    {
+    }
+
+    void starting(std::shared_ptr<mir::scene::Session> const&) override
+    {
+    }
+
+    void stopping(std::shared_ptr<mir::scene::Session> const&) override
+    {
+    }
+
+    void focused(std::shared_ptr<mir::scene::Session> const&) override
+    {
+    }
+
+    void unfocused() override
+    {
+    }
+
+    void surface_created(
+        mir::scene::Session&,
+        std::shared_ptr<mir::scene::Surface> const& surface) override
+    {
+        auto state_accessor = state.lock();
+        if (std::this_thread::get_id() == state_accessor->wayland_thread)
+        {
+            if (listeners.last_wl_window == nullptr)
+            {
+                BOOST_THROW_EXCEPTION((
+                    std::runtime_error{
+                        "Called Shell::create_surface() without first creating a wl_shell_surface?"}));
+            }
+
+            auto stream = surface->primary_buffer_stream();
+            auto wl_surface = state_accessor->stream_map.at(stream);
+
+            state_accessor->surface_map[wl_surface] = surface;
+        }
+    }
+
+    void destroying_surface(
+        mir::scene::Session&,
+        std::shared_ptr<mir::scene::Surface> const&) override
+    {
+        // TODO: Maybe delete from map?
+    }
+
+    void buffer_stream_created(
+        mir::scene::Session&,
+        std::shared_ptr<mir::frontend::BufferStream> const& stream) override
+    {
+        auto state_accessor = state.lock();
+        if (std::this_thread::get_id() == state_accessor->wayland_thread)
+        {
+            if (listeners.last_wl_surface == nullptr)
+            {
+                BOOST_THROW_EXCEPTION((
+                    std::runtime_error{"BufferStream created without first constructing a wl_surface?"}));
+            }
+
+            state_accessor->stream_map[stream] = listeners.last_wl_surface;
+            listeners.last_wl_surface = nullptr;
+        }
+    }
+
+    void buffer_stream_destroyed(
+        mir::scene::Session&,
+        std::shared_ptr<mir::frontend::BufferStream> const& stream) override
+    {
+        state.lock()->stream_map.erase(stream);
+    }
+
+    void init(wl_display* display)
+    {
+        state.lock()->wayland_thread = std::this_thread::get_id();
+
+        listeners.client_listener.notify = &client_created;
+
+        wl_display_add_client_created_listener(display, &listeners.client_listener);
+    }
+
+    std::weak_ptr<mir::scene::Surface> surface_for_resource(wl_resource* surface)
+    {
+        if (strcmp(wl_resource_get_class(surface), "wl_surface") != 0)
+        {
+            BOOST_THROW_EXCEPTION((
+                std::logic_error{
+                    std::string{"Expected a wl_surface, got: "} +
+                    wl_resource_get_class(surface)
+                }));
+        }
+
+        auto state_accessor = state.lock();
+        return state_accessor->surface_map.at(surface);
+    }
+
+    wl_client* client_for_fd(int client_socket)
+    {
+        return listeners.state->lock()->client_session_map.at(client_socket);
+    }
+
+    void associate_client_socket(int client_socket)
+    {
+        auto state_accessor = state.wait_for(
+            [](State& state) { return static_cast<bool>(state.latest_client)    ; },
+            std::chrono::seconds{30});
+        state_accessor->client_session_map[client_socket] = state_accessor->latest_client.value();
+        state_accessor->latest_client = {};
+    }
+
+private:
+    struct Listeners;
+    struct ResourceListener
+    {
+        ResourceListener(Listeners* const listeners)
+            : listeners{listeners}
+        {
+        }
+
+        wl_listener resource_listener;
+        Listeners* const listeners;
+    };
+    struct State
+    {
+        std::thread::id wayland_thread;
+        std::unordered_map<wl_resource*, std::weak_ptr<mir::scene::Surface>> surface_map;
+        std::unordered_map<std::shared_ptr<mir::frontend::BufferStream>, wl_resource*> stream_map;
+
+        std::experimental::optional<wl_client*> latest_client;
+        std::unordered_map<ClientFd, wl_client*> client_session_map;
+        std::unordered_map<wl_client*, ResourceListener> resource_listener;
+    };
+    wlcs::WaitableMutex<State> state;
+
+    struct Listeners
+    {
+        Listeners(wlcs::WaitableMutex<State>* const state)
+            : state{state}
+        {
+        }
+
+        wl_listener client_listener;
+
+        wl_resource* last_wl_surface{nullptr};
+        wl_resource* last_wl_window{nullptr};
+
+        wlcs::WaitableMutex<State>* const state;
+    } listeners;
+
+    static void resource_created(wl_listener* listener, void* ctx)
+    {
+        auto resource = static_cast<wl_resource*>(ctx);
+        ResourceListener* resource_listener;
+        resource_listener =
+            wl_container_of(listener, resource_listener, resource_listener);
+
+        bool const is_surface = strcmp(
+            wl_resource_get_class(resource),
+            "wl_surface") == 0;
+
+        bool const is_window = strcmp(
+                wl_resource_get_class(resource),
+                "wl_shell_surface") == 0 ||
+            strcmp(
+                wl_resource_get_class(resource),
+                "zxdg_surface_v6") == 0;
+
+        if (is_surface)
+        {
+            resource_listener->listeners->last_wl_surface = resource;
+        }
+        else if (is_window)
+        {
+            resource_listener->listeners->last_wl_window = resource;
+        }
+    }
+
+    static void client_created(wl_listener* listener, void* ctx)
+    {
+        auto client = static_cast<wl_client*>(ctx);
+        Listeners* listeners;
+        listeners =
+            wl_container_of(listener, listeners, client_listener);
+
+        wl_listener* resource_listener;
+        {
+            auto state_accessor = listeners->state->lock();
+            state_accessor->latest_client = client;
+            auto rl = state_accessor->resource_listener.emplace(client, listeners);
+            rl.first->second.resource_listener.notify = &resource_created;
+            resource_listener = &rl.first->second.resource_listener;
+        }
+        listeners->state->notify_all();
+
+        wl_client_add_resource_created_listener(client, resource_listener);
+    }
+};
+
+namespace
+{
+class WaylandExecutor : public mir::Executor
+{
+public:
+    void spawn (std::function<void()>&& work) override
+    {
+        {
+            std::lock_guard<std::recursive_mutex> lock{mutex};
+            workqueue.emplace_back(std::move(work));
+        }
+        if (auto err = eventfd_write(notify_fd, 1))
+        {
+            BOOST_THROW_EXCEPTION((std::system_error{err, std::system_category(), "eventfd_write failed to notify event loop"}));
+        }
+    }
+
+    /**
+     * Get an Executor which dispatches onto a wl_event_loop
+     *
+     * \note    The executor may outlive the wl_event_loop, but no tasks will be dispatched
+     *          after the wl_event_loop is destroyed.
+     *
+     * \param [in]  loop    The event loop to dispatch on
+     * \return              An Executor that queues onto the wl_event_loop
+     */
+    static std::shared_ptr<mir::Executor> executor_for_event_loop(wl_event_loop* loop)
+    {
+        if (auto notifier = wl_event_loop_get_destroy_listener(loop, &on_display_destruction))
+        {
+            DestructionShim* shim;
+            shim = wl_container_of(notifier, shim, destruction_listener);
+
+            return shim->executor;
+        }
+        else
+        {
+            auto const executor = std::shared_ptr<WaylandExecutor>{new WaylandExecutor{loop}};
+            auto shim = std::make_unique<DestructionShim>(executor);
+
+            shim->destruction_listener.notify = &on_display_destruction;
+            wl_event_loop_add_destroy_listener(loop, &(shim.release())->destruction_listener);
+
+            return executor;
+        }
+    }
+
+private:
+    WaylandExecutor(wl_event_loop* loop)
+        : notify_fd{eventfd(0, EFD_CLOEXEC | EFD_SEMAPHORE | EFD_NONBLOCK)},
+          notify_source{wl_event_loop_add_fd(loop, notify_fd, WL_EVENT_READABLE, &on_notify, this)}
+    {
+        if (notify_fd == mir::Fd::invalid)
+        {
+            BOOST_THROW_EXCEPTION((std::system_error{
+                errno,
+                std::system_category(),
+                "Failed to create IPC pause notification eventfd"}));
+        }
+    }
+
+    std::function<void()> get_work()
+    {
+        std::lock_guard<std::recursive_mutex> lock{mutex};
+        if (!workqueue.empty())
+        {
+            auto const work = std::move(workqueue.front());
+            workqueue.pop_front();
+            return work;
+        }
+
+        return {};
+    }
+
+    static int on_notify(int fd, uint32_t, void* data)
+    {
+        auto executor = static_cast<WaylandExecutor*>(data);
+
+        eventfd_t unused;
+        if (auto err = eventfd_read(fd, &unused))
+        {
+            mir::log(
+                mir::logging::Severity::error,
+                "wlcs-integration",
+                "eventfd_read failed to consume wakeup notification: %s (%i)",
+                strerror(err),
+                err);
+        }
+
+        std::unique_lock<std::recursive_mutex> lock{executor->mutex};
+        while (auto work = executor->get_work())
+        {
+            try
+            {
+                work();
+            }
+            catch(...)
+            {
+                mir::log(
+                    mir::logging::Severity::critical,
+                    "wlcs-integration",
+                    std::current_exception(),
+                    "Exception processing Wayland event loop work item");
+            }
+        }
+
+        return 0;
+    }
+
+    static void on_display_destruction(wl_listener* listener, void*)
+    {
+        DestructionShim* shim;
+        shim = wl_container_of(listener, shim, destruction_listener);
+
+        {
+            std::lock_guard<std::recursive_mutex> lock{shim->executor->mutex};
+            wl_event_source_remove(shim->executor->notify_source);
+        }
+        delete shim;
+    }
+
+    std::recursive_mutex mutex;
+    mir::Fd const notify_fd;
+    std::deque<std::function<void()>> workqueue;
+
+    wl_event_source* const notify_source;
+
+    struct DestructionShim
+    {
+        explicit DestructionShim(std::shared_ptr<WaylandExecutor> const& executor)
+            : executor{executor}
+        {
+        }
+
+        std::shared_ptr<WaylandExecutor> const executor;
+        wl_listener destruction_listener;
+    };
+    static_assert(
+        std::is_standard_layout<DestructionShim>::value,
+        "DestructionShim must be Standard Layout for wl_container_of to be defined behaviour");
+};
+}
+
+class InputEventListener;
+
+struct MirWlcsDisplayServer : mtf::AsyncServerRunner
+{
+    testing::NiceMock<mir::test::doubles::MockGL> mockgl;
+    std::shared_ptr<ResourceMapper> const resource_mapper{std::make_shared<ResourceMapper>()};
+    std::shared_ptr<InputEventListener> event_listener;
+    std::shared_ptr<mir::Executor> executor;
+    std::atomic<double> cursor_x{0}, cursor_y{0};
+
+    mir::test::Signal started;
+};
+
+class InputEventListener : public mir::input::SeatObserver
+{
+public:
+    InputEventListener(MirWlcsDisplayServer& runner)
+        : runner{runner}
+    {
+    }
+
+    std::shared_ptr<mir::test::Signal> expect_event_with_time(
+        std::chrono::nanoseconds event_time)
+    {
+        auto done_signal = std::make_shared<mir::test::Signal>();
+        expected_events.lock()->insert(std::make_pair(event_time, done_signal));
+        return done_signal;
+    }
+
+    void seat_add_device(uint64_t /*id*/) override
+    {
+    }
+
+    void seat_remove_device(uint64_t /*id*/) override
+    {
+    }
+
+    void seat_dispatch_event(std::shared_ptr<MirEvent const> const& event) override
+    {
+        auto iev = mir_event_get_input_event(event.get());
+        auto event_time = std::chrono::nanoseconds{mir_input_event_get_event_time(iev)};
+
+        auto expected_events_accessor = expected_events.lock();
+        if (expected_events_accessor->count(event_time))
+        {
+            expected_events_accessor->at(event_time)->raise();
+            expected_events_accessor->erase(event_time);
+        }
+    }
+
+    void seat_set_key_state(
+        uint64_t /*id*/,
+        std::vector<uint32_t> const& /*scan_codes*/) override
+    {
+    }
+
+    void seat_set_pointer_state(
+        uint64_t /*id*/,
+        unsigned /*buttons*/) override
+    {
+    }
+
+    void seat_set_cursor_position(
+        float cursor_x,
+        float cursor_y) override
+    {
+        runner.cursor_x = cursor_x;
+        runner.cursor_y = cursor_y;
+    }
+
+    void seat_set_confinement_region_called(
+        mir::geometry::Rectangles const& /*regions*/) override
+    {
+    }
+
+    void seat_reset_confinement_regions() override
+    {
+    }
+
+private:
+    wlcs::Mutex<std::unordered_map<std::chrono::nanoseconds, std::shared_ptr<mir::test::Signal>>> expected_events;
+    MirWlcsDisplayServer& runner;
+};
+
+template<typename T>
+void emit_mir_event(MirWlcsDisplayServer* runner,
+                    mir::UniqueModulePtr<mir_test_framework::FakeInputDevice>& emitter,
+                    T event)
+{
+    auto event_time = std::chrono::duration_cast<std::chrono::nanoseconds>(
+        std::chrono::steady_clock::now().time_since_epoch());
+
+    auto event_sent = runner->event_listener->expect_event_with_time(event_time);
+
+    emitter->emit_event(event.with_event_time(event_time));
+
+    EXPECT_THAT(event_sent->wait_for(a_long_time), testing::Eq(true)) << "fake event failed to go through";
+}
+}
+
+void wlcs_server_start(WlcsDisplayServer* server)
+{
+    auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+    runner->start_server();
+
+    runner->started.wait_for(a_long_time);
+}
+
+void wlcs_server_stop(WlcsDisplayServer* server)
+{
+    auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+    runner->stop_server();
+}
+
+
+WlcsDisplayServer* wlcs_create_server(int argc, char const** argv)
+{
+    auto runner = new MirWlcsDisplayServer;
+    runner->add_to_environment("MIR_SERVER_PLATFORM_GRAPHICS_LIB", mtf::server_platform("graphics-dummy.so").c_str());
+    runner->add_to_environment("MIR_SERVER_PLATFORM_INPUT_LIB", mtf::server_platform("input-stub.so").c_str());
+    runner->add_to_environment("MIR_SERVER_ENABLE_KEY_REPEAT", "false");
+    runner->add_to_environment("MIR_SERVER_NO_FILE", "");
+    runner->add_to_environment("MIR_SERVER_WAYLAND_SOCKET_NAME", "wlcs-tests");
+    runner->add_to_environment("WAYLAND_DISPLAY", "wlcs-tests");
+    runner->server.override_the_display_buffer_compositor_factory([]
+    {
+        return std::make_shared<mtf::HeadlessDisplayBufferCompositorFactory>();
+    });
+
+    runner->server.override_the_session_listener(
+        [runner]()
+        {
+            return runner->resource_mapper;
+        });
+
+    runner->event_listener = std::make_shared<InputEventListener>(*runner);
+
+    runner->server.set_command_line(argc, argv);
+
+    runner->server.add_init_callback(
+        [runner]()
+        {
+            runner->server.run_on_wayland_display(
+                [runner](auto wayland_display)
+                {
+                    runner->resource_mapper->init(wayland_display);
+                    runner->executor = WaylandExecutor::executor_for_event_loop(
+                        wl_display_get_event_loop(wayland_display));
+
+                    // Execute all observations on the Wayland event loop…
+                    runner->server.the_seat_observer_registrar()->register_interest(
+                        runner->event_listener,
+                        *runner->executor);
+
+                    runner->started.raise();
+                });
+        });
+
+    runner->server.wrap_cursor_listener(
+        [runner](auto const& wrappee)
+        {
+            class ListenerWrapper : public mir::input::CursorListener
+            {
+            public:
+                ListenerWrapper(
+                    MirWlcsDisplayServer* runner,
+                    std::shared_ptr<mir::input::CursorListener> const& wrapped)
+                    : runner{runner},
+                      wrapped{wrapped}
+                {
+                }
+
+                void cursor_moved_to(float abs_x, float abs_y) override
+                {
+                    runner->cursor_x = abs_x;
+                    runner->cursor_y = abs_y;
+                    wrapped->cursor_moved_to(abs_x, abs_y);
+                }
+
+            private:
+                MirWlcsDisplayServer* const runner;
+                std::shared_ptr<mir::input::CursorListener> const wrapped;
+            };
+            return std::make_shared<ListenerWrapper>(runner, wrappee);
+        });
+
+    return reinterpret_cast<WlcsDisplayServer*>(runner);
+}
+
+void wlcs_destroy_server(WlcsDisplayServer* server)
+{
+    auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+    delete runner;
+}
+
+int wlcs_server_create_client_socket(WlcsDisplayServer* server)
+{
+    auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+    try
+    {
+        auto client_fd = fcntl(
+            runner->server.open_wayland_client_socket(),
+            F_DUPFD_CLOEXEC,
+            3);
+
+        runner->resource_mapper->associate_client_socket(client_fd);
+
+        return client_fd;
+    }
+    catch (std::exception const&)
+    {
+        mir::log(
+            mir::logging::Severity::critical,
+            "wlcs-bindings",
+            std::current_exception(),
+            "Failed to create Wayland client socket");
+    }
+
+    return -1;
+}
+
+struct FakePointer
+{
+    decltype(mtf::add_fake_input_device(mi::InputDeviceInfo())) pointer;
+    MirWlcsDisplayServer* runner;
+};
+
+WlcsPointer* wlcs_server_create_pointer(WlcsDisplayServer* server)
+{
+    auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+    auto constexpr uid = "mouse-uid";
+
+    class DeviceObserver : public mir::input::InputDeviceObserver
+    {
+    public:
+        DeviceObserver(std::shared_ptr<mir::test::Signal> const& done)
+            : done{done}
+        {
+        }
+
+        void device_added(std::shared_ptr<mir::input::Device> const& device) override
+        {
+            if (device->unique_id() == uid)
+                seen_device = true;
+        }
+
+        void device_changed(std::shared_ptr<mir::input::Device> const&) override
+        {
+        }
+
+        void device_removed(std::shared_ptr<mir::input::Device> const&) override
+        {
+        }
+
+        void changes_complete() override
+        {
+            if (seen_device)
+                done->raise();
+        }
+
+    private:
+        std::shared_ptr<mir::test::Signal> const done;
+        bool seen_device{false};
+    };
+
+    auto mouse_added = std::make_shared<mir::test::Signal>();
+    auto observer = std::make_shared<DeviceObserver>(mouse_added);
+    runner->server.the_input_device_hub()->add_observer(observer);
+
+
+    auto fake_mouse = mtf::add_fake_input_device(
+        mi::InputDeviceInfo{"mouse", uid, mi::DeviceCapability::pointer});
+
+    mouse_added->wait_for(a_long_time);
+    runner->executor->spawn([observer=std::move(observer), the_input_device_hub=runner->server.the_input_device_hub()]
+        { the_input_device_hub->remove_observer(observer); });
+
+    auto fake_pointer = new FakePointer;
+    fake_pointer->runner = runner;
+    fake_pointer->pointer = std::move(fake_mouse);
+
+    return reinterpret_cast<WlcsPointer*>(fake_pointer);
+}
+
+void wlcs_destroy_pointer(WlcsPointer* pointer)
+{
+    delete reinterpret_cast<FakePointer*>(pointer);
+}
+
+void wlcs_pointer_move_relative(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y)
+{
+    auto device = reinterpret_cast<FakePointer*>(pointer);
+
+    auto event = mir::input::synthesis::a_pointer_event()
+                    .with_movement(wl_fixed_to_int(x), wl_fixed_to_int(y));
+
+    emit_mir_event(device->runner, device->pointer, event);
+}
+
+void wlcs_pointer_move_absolute(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y)
+{
+    auto device = reinterpret_cast<FakePointer*>(pointer);
+
+    auto rel_x = wl_fixed_to_double(x) - device->runner->cursor_x;
+    auto rel_y = wl_fixed_to_double(y) - device->runner->cursor_y;
+
+    wlcs_pointer_move_relative(pointer, wl_fixed_from_double(rel_x), wl_fixed_from_double(rel_y));
+}
+
+void wlcs_pointer_button_down(WlcsPointer* pointer, int button)
+{
+    auto device = reinterpret_cast<FakePointer*>(pointer);
+
+    auto event = mir::input::synthesis::a_button_down_event()
+                    .of_button(button);
+
+    emit_mir_event(device->runner, device->pointer, event);
+}
+
+void wlcs_pointer_button_up(WlcsPointer* pointer, int button)
+{
+    auto device = reinterpret_cast<FakePointer*>(pointer);
+
+    auto event = mir::input::synthesis::a_button_up_event()
+                    .of_button(button);
+
+    emit_mir_event(device->runner, device->pointer, event);
+}
+
+struct FakeTouch
+{
+    decltype(mtf::add_fake_input_device(mi::InputDeviceInfo())) touch;
+    int last_x{0}, last_y{0};
+    MirWlcsDisplayServer* runner;
+};
+
+WlcsTouch* wlcs_server_create_touch(WlcsDisplayServer* server)
+{
+    auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+    auto constexpr uid = "touch-uid";
+
+    class DeviceObserver : public mir::input::InputDeviceObserver
+    {
+    public:
+        DeviceObserver(std::shared_ptr<mir::test::Signal> const& done)
+            : done{done}
+        {
+        }
+
+        void device_added(std::shared_ptr<mir::input::Device> const& device) override
+        {
+            if (device->unique_id() == uid)
+                seen_device = true;
+        }
+
+        void device_changed(std::shared_ptr<mir::input::Device> const&) override
+        {
+        }
+
+        void device_removed(std::shared_ptr<mir::input::Device> const&) override
+        {
+        }
+
+        void changes_complete() override
+        {
+            if (seen_device)
+                done->raise();
+        }
+
+    private:
+        std::shared_ptr<mir::test::Signal> const done;
+        bool seen_device{false};
+    };
+
+    auto touch_added = std::make_shared<mir::test::Signal>();
+    auto observer = std::make_shared<DeviceObserver>(touch_added);
+    runner->server.the_input_device_hub()->add_observer(observer);
+
+    auto fake_touch_dev = mtf::add_fake_input_device(
+        mi::InputDeviceInfo{"touch", uid, mi::DeviceCapability::multitouch});
+
+    touch_added->wait_for(a_long_time);
+    runner->executor->spawn([observer=std::move(observer), the_input_device_hub=runner->server.the_input_device_hub()]
+        { the_input_device_hub->remove_observer(observer); });
+
+    auto fake_touch = new FakeTouch;
+    fake_touch->runner = runner;
+    fake_touch->touch = std::move(fake_touch_dev);
+
+    return reinterpret_cast<WlcsTouch*>(fake_touch);
+}
+
+void wlcs_destroy_touch(WlcsTouch* touch)
+{
+    delete reinterpret_cast<FakeTouch*>(touch);
+}
+
+void wlcs_touch_down(WlcsTouch* touch, int x, int y)
+{
+    auto device = reinterpret_cast<FakeTouch*>(touch);
+
+    device->last_x = x;
+    device->last_y = y;
+
+    auto event = mir::input::synthesis::a_touch_event()
+                    .with_action(mir::input::synthesis::TouchParameters::Action::Tap)
+                    .at_position({x, y});
+
+    emit_mir_event(device->runner, device->touch, event);
+}
+
+void wlcs_touch_move(WlcsTouch* touch, int x, int y)
+{
+    auto device = reinterpret_cast<FakeTouch*>(touch);
+
+    device->last_x = x;
+    device->last_y = y;
+
+    auto event = mir::input::synthesis::a_touch_event()
+                    .with_action(mir::input::synthesis::TouchParameters::Action::Move)
+                    .at_position({x, y});
+
+    emit_mir_event(device->runner, device->touch, event);
+}
+
+void wlcs_touch_up(WlcsTouch* touch)
+{
+    auto device = reinterpret_cast<FakeTouch*>(touch);
+
+    auto event = mir::input::synthesis::a_touch_event()
+                    .with_action(mir::input::synthesis::TouchParameters::Action::Release)
+                    .at_position({device->last_x, device->last_y});
+
+    emit_mir_event(device->runner, device->touch, event);
+}
+
+void wlcs_server_position_window_absolute(WlcsDisplayServer* server, wl_display* client, wl_surface* surface, int x, int y)
+{
+    auto runner = reinterpret_cast<MirWlcsDisplayServer*>(server);
+
+    try
+    {
+        auto const fd = wl_display_get_fd(client);
+        auto const client = runner->resource_mapper->client_for_fd(fd);
+        auto const id = wl_proxy_get_id(reinterpret_cast<wl_proxy*>(surface));
+
+        auto resource = wl_client_get_object(client, id);
+
+        auto mir_surface = runner->resource_mapper->surface_for_resource(resource);
+
+        if (auto live_surface = mir_surface.lock())
+        {
+            live_surface->move_to(mir::geometry::Point{x, y});
+        }
+        else
+        {
+            abort();
+            // TODO: log? Error handling?
+        }
+    }
+    catch(std::out_of_range const&)
+    {
+        abort();
+        // TODO: Error handling.
+    }
+}
diff --git a/include/active_listeners.h b/include/active_listeners.h
new file mode 100644 (file)
index 0000000..8f0de8b
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_ACTIVE_LISTENERS_H
+#define WLCS_ACTIVE_LISTENERS_H
+
+#include <mutex>
+#include <set>
+
+namespace wlcs
+{
+class ActiveListeners
+{
+public:
+    void add(void* listener)
+    {
+        std::lock_guard<decltype(mutex)> lock{mutex};
+        listeners.insert(listener);
+    }
+
+    void del(void* listener)
+    {
+        std::lock_guard<decltype(mutex)> lock{mutex};
+        listeners.erase(listener);
+    }
+
+    bool includes(void* listener) const
+    {
+        std::lock_guard<decltype(mutex)> lock{mutex};
+        return listeners.find(listener) != end(listeners);
+    }
+
+private:
+    std::mutex mutable mutex;
+    std::set<void*> listeners;
+};
+}
+
+#endif //WLCS_ACTIVE_LISTENERS_H
diff --git a/include/data_device.h b/include/data_device.h
new file mode 100644 (file)
index 0000000..35adce5
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_DATA_DEVICE_H
+#define WLCS_DATA_DEVICE_H
+
+#include "active_listeners.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <wayland-client.h>
+
+#include <memory>
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(wl_data_device_manager)
+
+class DataSource
+{
+public:
+    DataSource() = default;
+
+    explicit DataSource(struct wl_data_source* ds) : self{ds, deleter} {}
+
+    operator struct wl_data_source*() const { return self.get(); }
+
+    void reset() { self.reset(); }
+
+    void reset(struct wl_data_source* ds) { self.reset(ds, deleter); }
+
+    friend void wl_data_source_destroy(DataSource const&) = delete;
+
+private:
+    static void deleter(struct wl_data_source* ds) { wl_data_source_destroy(ds); }
+
+    std::shared_ptr<struct wl_data_source> self;
+};
+
+struct DataDeviceListener
+{
+    DataDeviceListener(struct wl_data_device* data_device)
+    {
+        active_listeners.add(this);
+        wl_data_device_add_listener(data_device, &thunks, this);
+    }
+
+    virtual ~DataDeviceListener() { active_listeners.del(this); }
+
+    DataDeviceListener(DataDeviceListener const&) = delete;
+    DataDeviceListener& operator=(DataDeviceListener const&) = delete;
+
+    virtual void data_offer(
+        struct wl_data_device* wl_data_device,
+        struct wl_data_offer* id);
+
+    virtual void enter(
+        struct wl_data_device* wl_data_device,
+        uint32_t serial,
+        struct wl_surface* surface,
+        wl_fixed_t x,
+        wl_fixed_t y,
+        struct wl_data_offer* id);
+
+    virtual void leave(struct wl_data_device* wl_data_device);
+
+    virtual void motion(
+        struct wl_data_device* wl_data_device,
+        uint32_t time,
+        wl_fixed_t x,
+        wl_fixed_t y);
+
+    virtual void drop(struct wl_data_device* wl_data_device);
+
+    virtual void selection(
+        struct wl_data_device* wl_data_device,
+        struct wl_data_offer* id);
+
+private:
+    static void data_offer(
+        void* data,
+        struct wl_data_device* wl_data_device,
+        struct wl_data_offer* id);
+
+    static void enter(
+        void* data,
+        struct wl_data_device* wl_data_device,
+        uint32_t serial,
+        struct wl_surface* surface,
+        wl_fixed_t x,
+        wl_fixed_t y,
+        struct wl_data_offer* id);
+
+    static void leave(void* data, struct wl_data_device* wl_data_device);
+
+    static void motion(
+        void* data,
+        struct wl_data_device* wl_data_device,
+        uint32_t time,
+        wl_fixed_t x,
+        wl_fixed_t y);
+
+    static void drop(void* data, struct wl_data_device* wl_data_device);
+
+    static void selection(
+        void* data,
+        struct wl_data_device* wl_data_device,
+        struct wl_data_offer* id);
+
+
+    static ActiveListeners active_listeners;
+    constexpr static wl_data_device_listener thunks =
+        {
+            &data_offer,
+            &enter,
+            &leave,
+            &motion,
+            &drop,
+            &selection
+        };
+};
+
+struct DataOfferListener
+{
+    DataOfferListener() { active_listeners.add(this); }
+    virtual ~DataOfferListener() { active_listeners.del(this); }
+
+    DataOfferListener(DataOfferListener const&) = delete;
+    DataOfferListener& operator=(DataOfferListener const&) = delete;
+
+    void listen_to(struct wl_data_offer* data_offer)
+    {
+        wl_data_offer_add_listener(data_offer, &thunks, this);
+    }
+
+    virtual void offer(struct wl_data_offer* data_offer, char const* mime_type);
+
+    virtual void source_actions(struct wl_data_offer* data_offer, uint32_t dnd_actions);
+
+    virtual void action(struct wl_data_offer* data_offer, uint32_t dnd_action);
+
+private:
+    static void offer(void* data, struct wl_data_offer* data_offer, char const* mime_type);
+
+    static void source_actions(void* data, struct wl_data_offer* data_offer, uint32_t dnd_actions);
+
+    static void action(void* data, struct wl_data_offer* data_offer, uint32_t dnd_action);
+
+    static ActiveListeners active_listeners;
+    constexpr static wl_data_offer_listener thunks =
+        {
+            &offer,
+            &source_actions,
+            &action
+        };
+};
+
+class DataDevice
+{
+public:
+    DataDevice() = default;
+
+    explicit DataDevice(struct wl_data_device* dd) : self{dd, deleter} {}
+
+    operator struct wl_data_device*() const { return self.get(); }
+
+    void reset() { self.reset(); }
+
+    void reset(struct wl_data_device* dd) { self.reset(dd, deleter); }
+
+    friend void wl_data_device_destroy(DataDevice const&) = delete;
+
+private:
+    static void deleter(struct wl_data_device* dd) { wl_data_device_destroy(dd); }
+
+    std::shared_ptr<struct wl_data_device> self;
+};
+}
+
+#endif //WLCS_DATA_DEVICE_H
diff --git a/include/gtest_helpers.h b/include/gtest_helpers.h
new file mode 100644 (file)
index 0000000..2ad1b92
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include <chrono>
+#include <ostream>
+#include <iomanip>
+
+namespace std
+{
+namespace chrono
+{
+/// GTest helper to pretty-print time-points
+template<typename Clock>
+void PrintTo(time_point<Clock> const& time, ostream* os)
+{
+    auto remainder = time.time_since_epoch();
+    auto const hours = duration_cast<chrono::hours>(remainder);
+    remainder -= hours;
+    auto const minutes = duration_cast<chrono::minutes>(remainder);
+    remainder -= minutes;
+    auto const seconds = duration_cast<chrono::seconds>(remainder);
+    remainder -= seconds;
+    auto const nsec = duration_cast<chrono::nanoseconds>(remainder);
+    (*os)
+        << hours.count()
+        << ":" << setw(2) << setfill('0') << minutes.count()
+        << ":" << setw(2) << setfill('0') << seconds.count()
+        << "." << setw(9) << setfill('0') << nsec.count();
+}
+}
+}
diff --git a/include/gtk_primary_selection.h b/include/gtk_primary_selection.h
new file mode 100644 (file)
index 0000000..4028dfe
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_PRIMARY_SELECTION_H
+#define WLCS_PRIMARY_SELECTION_H
+
+#include "generated/gtk-primary-selection-client.h"
+
+#include "active_listeners.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <memory>
+#include <mutex>
+#include <set>
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(gtk_primary_selection_device_manager)
+
+    class GtkPrimarySelectionSource
+    {
+    public:
+        using WrappedType = gtk_primary_selection_source;
+
+        GtkPrimarySelectionSource() = default;
+
+        explicit GtkPrimarySelectionSource(gtk_primary_selection_device_manager* manager) :
+                self{gtk_primary_selection_device_manager_create_source(manager), deleter} {}
+
+        operator WrappedType*() const { return self.get(); }
+
+        void reset() { self.reset(); }
+
+        void reset(WrappedType* source) { self.reset(source, deleter); }
+
+        friend void gtk_primary_selection_source_destroy(GtkPrimarySelectionSource const&) = delete;
+
+    private:
+        static void deleter(WrappedType* source) { gtk_primary_selection_source_destroy(source); }
+
+        std::shared_ptr<WrappedType> self;
+    };
+
+
+    class GtkPrimarySelectionDevice
+    {
+    public:
+        using WrappedType = gtk_primary_selection_device;
+
+        GtkPrimarySelectionDevice() = default;
+
+        GtkPrimarySelectionDevice(gtk_primary_selection_device_manager* manager, wl_seat* seat) :
+                self{gtk_primary_selection_device_manager_get_device(manager, seat), deleter} {}
+
+        operator WrappedType*() const { return self.get(); }
+
+        void reset() { self.reset(); }
+
+        void reset(WrappedType* device) { self.reset(device, deleter); }
+
+        friend void gtk_primary_selection_device_destroy(GtkPrimarySelectionDevice const&) = delete;
+
+    private:
+        static void deleter(WrappedType* device) { gtk_primary_selection_device_destroy(device); }
+
+        std::shared_ptr<WrappedType> self;
+    };
+
+    struct GtkPrimarySelectionDeviceListener
+    {
+        GtkPrimarySelectionDeviceListener(gtk_primary_selection_device* device)
+        {
+            active_listeners.add(this);
+            gtk_primary_selection_device_add_listener(device, &thunks, this);
+        }
+
+        virtual ~GtkPrimarySelectionDeviceListener() { active_listeners.del(this); }
+
+        GtkPrimarySelectionDeviceListener(GtkPrimarySelectionDeviceListener const&) = delete;
+        GtkPrimarySelectionDeviceListener& operator=(GtkPrimarySelectionDeviceListener const&) = delete;
+
+        virtual void data_offer(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer);
+
+        virtual void selection(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer);
+
+    private:
+        static void data_offer(void* data, gtk_primary_selection_device* device, gtk_primary_selection_offer* offer);
+
+        static void selection(void* data, gtk_primary_selection_device* device, gtk_primary_selection_offer* offer);
+
+        static ActiveListeners active_listeners;
+        constexpr static gtk_primary_selection_device_listener thunks =
+                {
+                        &data_offer,
+                        &selection
+                };
+    };
+
+    struct GtkPrimarySelectionOfferListener
+    {
+        GtkPrimarySelectionOfferListener() { active_listeners.add(this); }
+        virtual ~GtkPrimarySelectionOfferListener() { active_listeners.del(this); }
+
+        GtkPrimarySelectionOfferListener(GtkPrimarySelectionOfferListener const&) = delete;
+        GtkPrimarySelectionOfferListener& operator=(GtkPrimarySelectionOfferListener const&) = delete;
+
+        void listen_to(gtk_primary_selection_offer* offer)
+        {
+            gtk_primary_selection_offer_add_listener(offer, &thunks, this);
+        }
+
+        virtual void offer(gtk_primary_selection_offer* offer, const char* mime_type);
+
+    private:
+        static void offer(void* data, gtk_primary_selection_offer* offer, const char* mime_type);
+
+        static ActiveListeners active_listeners;
+        constexpr static gtk_primary_selection_offer_listener thunks = { &offer, };
+    };
+
+    struct GtkPrimarySelectionSourceListener
+    {
+        explicit GtkPrimarySelectionSourceListener(GtkPrimarySelectionSource const& source)
+        {
+            active_listeners.add(this);
+            gtk_primary_selection_source_add_listener(source, &thunks, this);
+        }
+
+        virtual ~GtkPrimarySelectionSourceListener() { active_listeners.del(this); }
+
+        GtkPrimarySelectionSourceListener(GtkPrimarySelectionSourceListener const&) = delete;
+        GtkPrimarySelectionSourceListener& operator=(GtkPrimarySelectionSourceListener const&) = delete;
+
+        virtual void send(gtk_primary_selection_source* source, const char* mime_type, int32_t fd);
+        virtual void cancelled(gtk_primary_selection_source* source);
+
+    private:
+        static void send(void* data, gtk_primary_selection_source* source, const char* mime_type, int32_t fd);
+        static void cancelled(void* data, gtk_primary_selection_source* source);
+
+        static ActiveListeners active_listeners;
+        constexpr static gtk_primary_selection_source_listener thunks = { &send, &cancelled };
+    };
+}
+
+#endif //WLCS_PRIMARY_SELECTION_H
diff --git a/include/helpers.h b/include/helpers.h
new file mode 100644 (file)
index 0000000..3a22001
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_HELPERS_H_
+#define WLCS_HELPERS_H_
+
+#include <cstddef>
+#include <memory>
+
+struct WlcsServerIntegration;
+
+namespace wlcs
+{
+namespace helpers
+{
+int create_anonymous_file(size_t size);
+
+void set_command_line(int argc, char const** argv);
+
+int get_argc();
+char const** get_argv();
+
+void set_entry_point(std::shared_ptr<WlcsServerIntegration const> const& entry_point);
+
+std::shared_ptr<WlcsServerIntegration const> get_test_hooks();
+}
+}
+
+#endif //WLCS_HELPERS_H_
diff --git a/include/in_process_server.h b/include/in_process_server.h
new file mode 100644 (file)
index 0000000..5178ec1
--- /dev/null
@@ -0,0 +1,374 @@
+/*
+ * Copyright © 2017-2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_IN_PROCESS_SERVER_H_
+#define WLCS_IN_PROCESS_SERVER_H_
+
+#include "generated/wayland-client.h"
+#include "generated/xdg-shell-unstable-v6-client.h"
+#include "generated/xdg-shell-client.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <functional>
+#include <experimental/optional>
+#include <unordered_map>
+#include <chrono>
+
+#include "shared_library.h"
+#include "wl_handle.h"
+
+#include <wayland-client.h>
+
+struct WlcsPointer;
+struct WlcsTouch;
+struct WlcsServerIntegration;
+
+struct zwlr_layer_shell_v1;
+
+namespace wlcs
+{
+class VersionSpecifier;
+
+WLCS_CREATE_INTERFACE_DESCRIPTOR(wl_surface)
+WLCS_CREATE_INTERFACE_DESCRIPTOR(wl_subsurface)
+
+/* We need a manually-specified descriptor for wl_output,
+ * as wl_output only has a destructor for version >= 3
+ */
+namespace
+{
+void send_release_if_supported(wl_output* to_destroy)
+{
+    if (wl_output_get_version(to_destroy) >= WL_OUTPUT_RELEASE_SINCE_VERSION)
+    {
+        wl_output_release(to_destroy);
+    }
+    else
+    {
+        wl_output_destroy(to_destroy);
+    }
+}
+}
+template<>
+struct WlInterfaceDescriptor<wl_output>
+{
+    static constexpr bool const has_specialisation = true;
+    static constexpr wl_interface const* const interface = &wl_output_interface;
+    static constexpr void (* const destructor)(wl_output*) = &send_release_if_supported;
+};
+
+
+class Pointer
+{
+public:
+    ~Pointer();
+    Pointer(Pointer&&);
+
+    void move_to(int x, int y);
+    void move_by(int dx, int dy);
+
+    void button_down(int button);
+    void button_up(int button);
+    void click(int button);
+
+    void left_button_down();
+    void left_button_up();
+    void left_click();
+
+private:
+    friend class Server;
+    template<typename Proxy>
+    Pointer(
+        WlcsPointer* raw_device,
+        std::shared_ptr<Proxy> const& proxy,
+        std::shared_ptr<void const> const& keep_dso_loaded);
+
+    class Impl;
+    std::unique_ptr<Impl> impl;
+};
+
+class Touch
+{
+public:
+    ~Touch();
+    Touch(Touch&&);
+
+    void down_at(int x, int y);
+    void move_to(int x, int y);
+    void up();
+
+private:
+    friend class Server;
+    template<typename Proxy>
+    Touch(
+        WlcsTouch* raw_device,
+        std::shared_ptr<Proxy> const& proxy,
+        std::shared_ptr<void const> const& keep_dso_loaded);
+
+    class Impl;
+    std::unique_ptr<Impl> impl;
+};
+
+class Surface;
+
+class Server
+{
+public:
+    Server(
+        std::shared_ptr<WlcsServerIntegration const> const& module,
+        int argc,
+        char const** argv);
+    ~Server();
+
+    int create_client_socket();
+
+    Pointer create_pointer();
+    Touch create_touch();
+
+    void move_surface_to(Surface& surface, int x, int y);
+
+    void start();
+    void stop();
+
+    std::shared_ptr<const std::unordered_map<std::string, uint32_t>> supported_extensions();
+private:
+    class Impl;
+    std::unique_ptr<Impl> const impl;
+};
+
+class Client;
+
+class Surface
+{
+public:
+    explicit Surface(Client& client);
+    virtual ~Surface();
+
+    Surface(Surface&& other);
+
+    operator wl_surface*() const;
+
+    void attach_buffer(int width, int height);
+    void add_frame_callback(std::function<void(int)> const& on_frame);
+    void attach_visible_buffer(int width, int height);
+    void run_on_destruction(std::function<void()> callback);
+
+    bool has_focus() const;
+    std::pair<int, int> pointer_position() const;
+
+    Client& owner() const;
+
+private:
+    class Impl;
+    std::unique_ptr<Impl> impl;
+};
+
+class Subsurface: public Surface
+{
+public:
+    static Subsurface create_visible(Surface& parent, int x, int y, int width, int height);
+
+    Subsurface(Surface& parent);
+    Subsurface(Subsurface &&);
+    ~Subsurface();
+
+    operator wl_subsurface*() const;
+
+    Surface& parent() const;
+
+private:
+    class Impl;
+    std::unique_ptr<Impl> impl;
+};
+
+class ShmBuffer
+{
+public:
+    ShmBuffer(Client& client, int width, int height);
+    ~ShmBuffer();
+
+    ShmBuffer(ShmBuffer&& other);
+
+    operator wl_buffer*() const;
+
+    void add_release_listener(std::function<bool()> const &on_release);
+
+private:
+    class Impl;
+    std::unique_ptr<Impl> impl;
+};
+
+struct OutputState
+{
+    OutputState(wl_output* output)
+        : output{output}
+    {
+    }
+
+    wl_output* output;
+    std::experimental::optional<std::pair<int, int>> geometry_position;
+    std::experimental::optional<std::pair<int, int>> mode_size;
+    std::experimental::optional<int> scale;
+};
+
+class Client
+{
+public:
+    Client(Server& server);
+    ~Client();
+
+    // Accessors
+
+    operator wl_display*() const;
+
+    wl_compositor* compositor() const;
+    wl_subcompositor* subcompositor() const;
+    wl_shm* shm() const;
+    wl_seat* seat() const;
+
+    void run_on_destruction(std::function<void()> callback);
+
+    ShmBuffer const& create_buffer(int width, int height);
+    Surface create_wl_shell_surface(int width, int height);
+    Surface create_xdg_shell_v6_surface(int width, int height);
+    Surface create_xdg_shell_stable_surface(int width, int height);
+    Surface create_visible_surface(int width, int height);
+
+    size_t output_count() const;
+    OutputState output_state(size_t index) const;
+    void add_output_done_notifier(size_t index, std::function<void()> const& notifier);
+
+    wl_shell* shell() const;
+    wl_pointer* the_pointer() const;
+    zxdg_shell_v6* xdg_shell_v6() const;
+    xdg_wm_base* xdg_shell_stable() const;
+    wl_surface* window_under_cursor() const;
+    wl_surface* touched_window() const;
+    std::pair<wl_fixed_t, wl_fixed_t> pointer_position() const;
+    std::pair<wl_fixed_t, wl_fixed_t> touch_position() const;
+
+    using PointerEnterNotifier =
+        std::function<bool(wl_surface*, wl_fixed_t x, wl_fixed_t y)>;
+    using PointerLeaveNotifier =
+        std::function<bool(wl_surface*)>;
+    using PointerMotionNotifier =
+        std::function<bool(wl_fixed_t x, wl_fixed_t y)>;
+    using PointerButtonNotifier =
+        std::function<bool(uint32_t serial, uint32_t button, bool is_down)>;
+    void add_pointer_enter_notification(PointerEnterNotifier const& on_enter);
+    void add_pointer_leave_notification(PointerLeaveNotifier const& on_leave);
+    void add_pointer_motion_notification(PointerMotionNotifier const& on_motion);
+    void add_pointer_button_notification(PointerButtonNotifier const& on_button);
+
+    void dispatch_until(
+        std::function<bool()> const& predicate,
+        std::chrono::seconds timeout = std::chrono::seconds{10});
+
+    template<typename WlType>
+    auto bind_if_supported(VersionSpecifier const& version) -> WlHandle<WlType>
+    {
+        return wrap_wl_object(
+            static_cast<WlType*>(bind_if_supported(*WlInterfaceDescriptor<WlType>::interface, version)));
+    }
+
+    auto bind_if_supported(wl_interface const& interface, VersionSpecifier const& version) const -> void*;
+
+    void roundtrip();
+
+private:
+    class Impl;
+    std::unique_ptr<Impl> const impl;
+};
+
+class ProtocolError : public std::system_error
+{
+public:
+    ProtocolError(wl_interface const* interface, uint32_t code)
+        : std::system_error(EPROTO, std::system_category(), "Wayland protocol error"),
+        interface_{interface},
+        code_{code},
+        message{
+            std::string{"Wayland protocol error: "} +
+            std::to_string(code) +
+            " on interface " +
+            interface_->name +
+            " v" +
+            std::to_string(interface->version)}
+    {
+    }
+
+    char const* what() const noexcept override
+    {
+        return message.c_str();
+    }
+
+    uint32_t error_code() const
+    {
+        return code_;
+    }
+
+    wl_interface const* interface() const
+    {
+        return interface_;
+    }
+
+private:
+    wl_interface const* const interface_;
+    uint32_t const code_;
+    std::string const message;
+};
+
+class ExtensionExpectedlyNotSupported : public std::runtime_error
+{
+public:
+    ExtensionExpectedlyNotSupported(char const* extension, VersionSpecifier const& version);
+};
+
+class Timeout : public std::runtime_error
+{
+public:
+    explicit Timeout(char const* message);
+};
+
+class InProcessServer : public testing::Test
+{
+public:
+    InProcessServer();
+
+    void SetUp() override;
+    void TearDown() override;
+
+    Server& the_server();
+private:
+    Server server;
+};
+
+class StartedInProcessServer : public InProcessServer
+{
+public:
+    StartedInProcessServer() { InProcessServer::SetUp(); }
+    ~StartedInProcessServer() { InProcessServer::TearDown(); }
+
+    void SetUp() override {}
+    void TearDown() override {}
+};
+}
+
+#endif //WLCS_IN_PROCESS_SERVER_H_
diff --git a/include/input_method.h b/include/input_method.h
new file mode 100644 (file)
index 0000000..0a16f89
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_INPUT_METHOD_H_
+#define WLCS_INPUT_METHOD_H_
+
+#include <string>
+#include <vector>
+#include "in_process_server.h"
+
+namespace wlcs
+{
+
+struct InputMethod
+{
+    InputMethod(std::string const& name) : name{name} {}
+    virtual ~InputMethod() = default;
+
+    struct Device
+    {
+        virtual ~Device() = default;
+        /// Can only be called while decice is up
+        virtual void down_at(std::pair<int, int> position) = 0;
+        /// Can only be called while device is down
+        virtual void move_to(std::pair<int, int> position) = 0;
+        /// Can only be called while device is down
+        virtual void up() = 0;
+    };
+
+    virtual auto create_device(wlcs::Server& server) -> std::unique_ptr<Device> = 0;
+    virtual auto current_surface(wlcs::Client const& client) -> wl_surface* = 0;
+    virtual auto position_on_surface(wlcs::Client const& client) -> std::pair<int, int> = 0;
+
+    static auto all_input_methods() -> std::vector<std::shared_ptr<InputMethod>>;
+
+    std::string const name;
+};
+
+struct PointerInputMethod : InputMethod
+{
+    PointerInputMethod() : InputMethod{"pointer"} {}
+
+    struct Pointer;
+
+    auto create_device(wlcs::Server& server) -> std::unique_ptr<Device> override;
+    auto current_surface(wlcs::Client const& client) -> wl_surface* override;
+    auto position_on_surface(wlcs::Client const& client) -> std::pair<int, int> override;
+};
+
+struct TouchInputMethod : InputMethod
+{
+    TouchInputMethod() : InputMethod{"touch"} {}
+
+    struct Touch;
+
+    auto create_device(wlcs::Server& server) -> std::unique_ptr<Device> override;
+    auto current_surface(wlcs::Client const& client) -> wl_surface* override;
+    auto position_on_surface(wlcs::Client const& client) -> std::pair<int, int> override;
+};
+
+}
+
+namespace std
+{
+std::ostream& operator<<(std::ostream& out, std::shared_ptr<wlcs::InputMethod> const& param);
+}
+
+#endif // WLCS_INPUT_METHOD_H_
diff --git a/include/layer_shell_v1.h b/include/layer_shell_v1.h
new file mode 100644 (file)
index 0000000..2e0ee49
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright © 2018-2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_LAYER_SHELL_V1_H
+#define WLCS_LAYER_SHELL_V1_H
+
+#include "in_process_server.h"
+#include "wl_handle.h"
+
+// Because _someone_ *cough*ddevault*cough* thought it would be a great idea to name an argument "namespace"
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wkeyword-macro"
+#endif
+#define namespace _namespace
+#include "generated/wlr-layer-shell-unstable-v1-client.h"
+#undef namespace
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_layer_shell_v1)
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_layer_surface_v1)
+
+class LayerSurfaceV1
+{
+public:
+    LayerSurfaceV1(
+        wlcs::Client& client,
+        wlcs::Surface& surface,
+        zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP,
+        wl_output* output = NULL,
+        const char *_namespace = "wlcs");
+    LayerSurfaceV1(LayerSurfaceV1 const&) = delete;
+    LayerSurfaceV1& operator=(LayerSurfaceV1 const&) = delete;
+
+    operator zwlr_layer_surface_v1*() const { return layer_surface; }
+
+    void dispatch_until_configure();
+    auto last_width() const -> int { return last_width_; }
+    auto last_height() const -> int { return last_height_; }
+
+private:
+    wlcs::Client& client;
+    WlHandle<zwlr_layer_shell_v1> layer_shell;
+    WlHandle<zwlr_layer_surface_v1> layer_surface;
+    int last_width_ = -1;
+    int last_height_ = -1;
+    int configure_count = 0;
+};
+
+}
+
+#endif // WLCS_LAYER_SHELL_V1_H
diff --git a/include/mutex.h b/include/mutex.h
new file mode 100644 (file)
index 0000000..76aeda9
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2 or 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_MUTEX_H_
+#define WLCS_MUTEX_H_
+
+#include <mutex>
+#include <condition_variable>
+#include <boost/throw_exception.hpp>
+
+namespace wlcs
+{
+/**
+ * Smart-pointer-esque accessor for Mutex<> protected data.
+ *
+ * Ensures exclusive access to the referenced data.
+ *
+ * \tparam Guarded Type of data guarded by the mutex.
+ */
+template<typename Guarded>
+class MutexGuard
+{
+public:
+    MutexGuard(std::unique_lock<std::mutex>&& lock, Guarded& value)
+        : value{value},
+          lock{std::move(lock)}
+    {
+    }
+    MutexGuard(MutexGuard&& from) = default;
+    ~MutexGuard() noexcept(false)
+    {
+        if (lock.owns_lock())
+        {
+            lock.unlock();
+        }
+    }
+
+    Guarded& operator*()
+    {
+        return value;
+    }
+    Guarded* operator->()
+    {
+        return &value;
+    }
+private:
+    Guarded& value;
+    std::unique_lock<std::mutex> lock;
+};
+
+/**
+ * A data-locking mutex
+ *
+ * This is a mutex which owns the data it guards, and can give out a
+ * smart-pointer-esque lock to lock and access it.
+ *
+ * \tparam Guarded  The type of data guarded by the mutex
+ */
+template<typename Guarded>
+class Mutex
+{
+public:
+    Mutex() = default;
+    Mutex(Guarded&& initial_value)
+        : value{std::move(initial_value)}
+    {
+    }
+
+    Mutex(Mutex const&) = delete;
+    Mutex& operator=(Mutex const&) = delete;
+
+    /**
+     * Lock the mutex and return an accessor for the protected data.
+     *
+     * \return A smart-pointer-esque accessor for the contained data.
+     *          While code has access to the MutexGuard it is guaranteed to have exclusive
+     *          access to the contained data.
+     */
+    MutexGuard<Guarded> lock()
+    {
+        return MutexGuard<Guarded>{std::unique_lock<std::mutex>{mutex}, value};
+    }
+
+protected:
+    std::mutex mutex;
+    Guarded value;
+};
+
+template<typename Guarded>
+class WaitableMutex : public Mutex<Guarded>
+{
+public:
+    using Mutex<Guarded>::Mutex;
+
+    template<typename Predicate, typename Rep, typename Period>
+    MutexGuard<Guarded> wait_for(Predicate predicate, std::chrono::duration<Rep, Period> timeout)
+    {
+        std::unique_lock<std::mutex> lock{this->mutex};
+        if (!notifier.wait_for(lock, timeout, [this, &predicate]() { return predicate(this->value); }))
+        {
+            BOOST_THROW_EXCEPTION((std::runtime_error{"Notification timeout"}));
+        }
+        return MutexGuard<Guarded>{std::move(lock), this->value};
+    }
+
+    void notify_all()
+    {
+        notifier.notify_all();
+    }
+private:
+    std::condition_variable notifier;
+};
+
+
+}
+
+#endif //WLCS_MUTEX_H_
diff --git a/include/pointer_constraints_unstable_v1.h b/include/pointer_constraints_unstable_v1.h
new file mode 100644 (file)
index 0000000..b84bb02
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_POINTER_CONSTRAINTS_UNSTABLE_V1_H
+#define WLCS_POINTER_CONSTRAINTS_UNSTABLE_V1_H
+
+#include "generated/pointer-constraints-unstable-v1-client.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+struct wl_pointer;
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_pointer_constraints_v1)
+
+class Client;
+
+class ZwpPointerConstraintsV1
+{
+public:
+    ZwpPointerConstraintsV1(Client& client);
+    ~ZwpPointerConstraintsV1();
+
+    operator zwp_pointer_constraints_v1*() const;
+
+    friend void zwp_pointer_constraints_v1_destroy(ZwpPointerConstraintsV1 const&) = delete;
+
+private:
+    WlHandle<zwp_pointer_constraints_v1> const manager;
+};
+
+class ZwpConfinedPointerV1
+{
+public:
+    ZwpConfinedPointerV1(
+        ZwpPointerConstraintsV1& manager,
+        wl_surface* surface,
+        wl_pointer* pointer,
+        wl_region* region,
+        uint32_t lifetime);
+
+    ~ZwpConfinedPointerV1();
+
+    MOCK_METHOD0(confined, void());
+    MOCK_METHOD0(unconfined, void());
+
+    operator zwp_confined_pointer_v1*() const;
+
+    friend void zwp_confined_pointer_v1_destroy(ZwpConfinedPointerV1 const&) = delete;
+
+private:
+    zwp_confined_pointer_v1* const relative_pointer;
+    uint32_t const version;
+    static zwp_confined_pointer_v1_listener const listener;
+};
+
+class ZwpLockedPointerV1
+{
+public:
+    ZwpLockedPointerV1(
+        ZwpPointerConstraintsV1& manager,
+        wl_surface* surface,
+        wl_pointer* pointer,
+        wl_region* region,
+        uint32_t lifetime);
+
+    ~ZwpLockedPointerV1();
+
+    MOCK_METHOD0(locked, void());
+    MOCK_METHOD0(unlocked, void());
+
+    operator zwp_locked_pointer_v1*() const;
+
+    friend void zwp_locked_pointer_v1_destroy(ZwpLockedPointerV1 const&) = delete;
+
+private:
+    zwp_locked_pointer_v1* const relative_pointer;
+    uint32_t const version;
+    static zwp_locked_pointer_v1_listener const listener;
+};
+
+}
+
+#endif //WLCS_POINTER_CONSTRAINTS_UNSTABLE_V1_H
diff --git a/include/primary_selection.h b/include/primary_selection.h
new file mode 100644 (file)
index 0000000..91798af
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_PRIMARY_SELECTION_H
+#define WLCS_PRIMARY_SELECTION_H
+
+#include "generated/primary-selection-unstable-v1-client.h"
+
+#include "active_listeners.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <memory>
+#include <mutex>
+#include <set>
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_primary_selection_device_manager_v1)
+
+    class PrimarySelectionSource
+    {
+    public:
+        using WrappedType = zwp_primary_selection_source_v1;
+
+        PrimarySelectionSource() = default;
+
+        explicit PrimarySelectionSource(zwp_primary_selection_device_manager_v1* manager) :
+                self{zwp_primary_selection_device_manager_v1_create_source(manager), deleter} {}
+
+        operator WrappedType*() const { return self.get(); }
+
+        void reset() { self.reset(); }
+
+        void reset(WrappedType* source) { self.reset(source, deleter); }
+
+        friend void zwp_primary_selection_source_v1_destroy(PrimarySelectionSource const&) = delete;
+
+    private:
+        static void deleter(WrappedType* source) { zwp_primary_selection_source_v1_destroy(source); }
+
+        std::shared_ptr<WrappedType> self;
+    };
+
+
+    class PrimarySelectionDevice
+    {
+    public:
+        using WrappedType = zwp_primary_selection_device_v1;
+
+        PrimarySelectionDevice() = default;
+
+        PrimarySelectionDevice(zwp_primary_selection_device_manager_v1* manager, wl_seat* seat) :
+                self{zwp_primary_selection_device_manager_v1_get_device(manager, seat), deleter} {}
+
+        operator WrappedType*() const { return self.get(); }
+
+        void reset() { self.reset(); }
+
+        void reset(WrappedType* device) { self.reset(device, deleter); }
+
+        friend void zwp_primary_selection_device_v1_destroy(PrimarySelectionDevice const&) = delete;
+
+    private:
+        static void deleter(WrappedType* device) { zwp_primary_selection_device_v1_destroy(device); }
+
+        std::shared_ptr<WrappedType> self;
+    };
+
+    struct PrimarySelectionDeviceListener
+    {
+        PrimarySelectionDeviceListener(zwp_primary_selection_device_v1* device)
+        {
+            active_listeners.add(this);
+            zwp_primary_selection_device_v1_add_listener(device, &thunks, this);
+        }
+
+        virtual ~PrimarySelectionDeviceListener() { active_listeners.del(this); }
+
+        PrimarySelectionDeviceListener(PrimarySelectionDeviceListener const&) = delete;
+        PrimarySelectionDeviceListener& operator=(PrimarySelectionDeviceListener const&) = delete;
+
+        virtual void data_offer(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer);
+
+        virtual void selection(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer);
+
+    private:
+        static void data_offer(void* data, zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer);
+
+        static void selection(void* data, zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer);
+
+        static ActiveListeners active_listeners;
+        constexpr static zwp_primary_selection_device_v1_listener thunks =
+                {
+                        &data_offer,
+                        &selection
+                };
+    };
+
+    struct PrimarySelectionOfferListener
+    {
+        PrimarySelectionOfferListener() { active_listeners.add(this); }
+        virtual ~PrimarySelectionOfferListener() { active_listeners.del(this); }
+
+        PrimarySelectionOfferListener(PrimarySelectionOfferListener const&) = delete;
+        PrimarySelectionOfferListener& operator=(PrimarySelectionOfferListener const&) = delete;
+
+        void listen_to(zwp_primary_selection_offer_v1* offer)
+        {
+            zwp_primary_selection_offer_v1_add_listener(offer, &thunks, this);
+        }
+
+        virtual void offer(zwp_primary_selection_offer_v1* offer, const char* mime_type);
+
+    private:
+        static void offer(void* data, zwp_primary_selection_offer_v1* offer, const char* mime_type);
+
+        static ActiveListeners active_listeners;
+        constexpr static zwp_primary_selection_offer_v1_listener thunks = { &offer, };
+    };
+
+    struct PrimarySelectionSourceListener
+    {
+        explicit PrimarySelectionSourceListener(PrimarySelectionSource const& source)
+        {
+            active_listeners.add(this);
+            zwp_primary_selection_source_v1_add_listener(source, &thunks, this);
+        }
+
+        virtual ~PrimarySelectionSourceListener() { active_listeners.del(this); }
+
+        PrimarySelectionSourceListener(PrimarySelectionSourceListener const&) = delete;
+        PrimarySelectionSourceListener& operator=(PrimarySelectionSourceListener const&) = delete;
+
+        virtual void send(zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd);
+        virtual void cancelled(zwp_primary_selection_source_v1* source);
+
+    private:
+        static void send(void* data, zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd);
+        static void cancelled(void* data, zwp_primary_selection_source_v1* source);
+
+        static ActiveListeners active_listeners;
+        constexpr static zwp_primary_selection_source_v1_listener thunks = { &send, &cancelled };
+    };
+}
+
+#endif //WLCS_PRIMARY_SELECTION_H
diff --git a/include/relative_pointer_unstable_v1.h b/include/relative_pointer_unstable_v1.h
new file mode 100644 (file)
index 0000000..7b68074
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef WLCS_RELATIVE_POINTER_UNSTABLE_V1_H
+#define WLCS_RELATIVE_POINTER_UNSTABLE_V1_H
+
+#include "generated/relative-pointer-unstable-v1-client.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+struct wl_pointer;
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwp_relative_pointer_manager_v1)
+
+class Client;
+
+class ZwpRelativePointerManagerV1
+{
+public:
+    ZwpRelativePointerManagerV1(Client& client);
+    ~ZwpRelativePointerManagerV1();
+
+    operator zwp_relative_pointer_manager_v1*() const;
+
+    friend void zwp_relative_pointer_manager_v1_destroy(ZwpRelativePointerManagerV1 const&) = delete;
+
+private:
+    WlHandle<zwp_relative_pointer_manager_v1> manager;
+};
+
+class ZwpRelativePointerV1
+{
+public:
+    ZwpRelativePointerV1(ZwpRelativePointerManagerV1& manager, wl_pointer* pointer);
+    ~ZwpRelativePointerV1();
+
+    MOCK_METHOD6(relative_motion,
+                 void(uint32_t utime_hi, uint32_t utime_lo,
+                     wl_fixed_t dx, wl_fixed_t dy,
+                     wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel));
+
+    operator zwp_relative_pointer_v1*() const;
+
+    friend void zwp_relative_pointer_v1_destroy(ZwpRelativePointerV1 const&) = delete;
+
+private:
+    zwp_relative_pointer_v1* const relative_pointer;
+    uint32_t const version;
+    static zwp_relative_pointer_v1_listener const listener;
+};
+
+}
+
+#endif //WLCS_RELATIVE_POINTER_UNSTABLE_V1_H
diff --git a/include/shared_library.h b/include/shared_library.h
new file mode 100644 (file)
index 0000000..ef275f7
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2 or 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#ifndef MIR_SHARED_LIBRARY_H_
+#define MIR_SHARED_LIBRARY_H_
+
+#include <string>
+
+namespace wlcs
+{
+class SharedLibrary
+{
+public:
+    explicit SharedLibrary(char const* library_name);
+    explicit SharedLibrary(std::string const& library_name);
+    ~SharedLibrary() noexcept;
+
+    template<typename FunctionPtr>
+    FunctionPtr load_function(char const* function_name) const
+    {
+        FunctionPtr result{};
+        (void*&)result = load_symbol(function_name);
+        return result;
+    }
+
+    template<typename FunctionPtr>
+    FunctionPtr load_function(std::string const& function_name) const
+    {
+        return load_function<FunctionPtr>(function_name.c_str());
+    }
+private:
+    void* const so;
+    void* load_symbol(char const* function_name) const;
+    SharedLibrary(SharedLibrary const&) = delete;
+    SharedLibrary& operator=(SharedLibrary const&) = delete;
+};
+}
+
+
+#endif /* MIR_SHARED_LIBRARY_H_ */
diff --git a/include/surface_builder.h b/include/surface_builder.h
new file mode 100644 (file)
index 0000000..fd8a42c
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_SURFACE_BUILDER_H_
+#define WLCS_SURFACE_BUILDER_H_
+
+#include <string>
+#include <vector>
+#include "in_process_server.h"
+
+namespace wlcs
+{
+
+struct SurfaceBuilder
+{
+    SurfaceBuilder(std::string const& name) : name{name} {}
+    virtual ~SurfaceBuilder() = default;
+
+    virtual auto build(
+        wlcs::Server& server,
+        wlcs::Client& client,
+        std::pair<int, int> position,
+        std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> = 0;
+
+    std::string const name;
+
+    static auto all_surface_types() -> std::vector<std::shared_ptr<SurfaceBuilder>>;
+    static auto toplevel_surface_types() -> std::vector<std::shared_ptr<SurfaceBuilder>>;
+};
+
+struct WlShellSurfaceBuilder : SurfaceBuilder
+{
+    WlShellSurfaceBuilder() : SurfaceBuilder{"wl_shell_surface"} {}
+
+    auto build(
+        wlcs::Server& server,
+        wlcs::Client& client,
+        std::pair<int, int> position,
+        std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> override;
+};
+
+struct XdgV6SurfaceBuilder : SurfaceBuilder
+{
+    XdgV6SurfaceBuilder() : SurfaceBuilder{"zxdg_surface_v6"} {}
+
+    auto build(
+        wlcs::Server& server,
+        wlcs::Client& client,
+        std::pair<int, int> position,
+        std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> override;
+};
+
+struct XdgStableSurfaceBuilder : SurfaceBuilder
+{
+    XdgStableSurfaceBuilder(int left_offset, int top_offset, int right_offset, int bottom_offset);
+
+    auto build(
+        wlcs::Server& server,
+        wlcs::Client& client,
+        std::pair<int, int> position,
+        std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> override;
+
+    int left_offset, top_offset, right_offset, bottom_offset;
+};
+
+struct SubsurfaceBuilder : SurfaceBuilder
+{
+    SubsurfaceBuilder(std::pair<int, int> offset);
+
+    auto build(
+        wlcs::Server& server,
+        wlcs::Client& client,
+        std::pair<int, int> position,
+        std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface> override;
+
+    std::pair<int, int> offset;
+};
+
+// TODO: popup surfaces
+}
+
+namespace std
+{
+std::ostream& operator<<(std::ostream& out, std::shared_ptr<wlcs::SurfaceBuilder> const& param);
+}
+
+#endif // WLCS_SURFACE_BUILDER_H_
diff --git a/include/version_specifier.h b/include/version_specifier.h
new file mode 100644 (file)
index 0000000..253840b
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_VERSION_SPECIFIER_H_
+#define WLCS_VERSION_SPECIFIER_H_
+
+#include <cstdint>
+#include <experimental/optional>
+
+namespace wlcs
+{
+class VersionSpecifier
+{
+public:
+    VersionSpecifier() = default;
+    virtual ~VersionSpecifier() = default;
+
+    virtual auto select_version(uint32_t max_version) const -> std::experimental::optional<uint32_t> = 0;
+    virtual auto describe() const -> std::string = 0;
+};
+
+class ExactlyVersion : public VersionSpecifier
+{
+public:
+    explicit ExactlyVersion(uint32_t version) noexcept;
+
+    auto select_version(uint32_t max_version) const -> std::experimental::optional<uint32_t> override;
+    auto describe() const -> std::string override;
+private:
+    uint32_t const version;
+};
+
+class AtLeastVersion : public VersionSpecifier
+{
+public:
+    explicit AtLeastVersion(uint32_t version) noexcept;
+
+    auto select_version(uint32_t max_version) const -> std::experimental::optional<uint32_t> override;
+    auto describe() const -> std::string override;
+private:
+    uint32_t const version;
+};
+
+extern VersionSpecifier const& AnyVersion;
+}
+
+#endif //WLCS_VERSION_SPECIFIER_H_
diff --git a/include/wl_handle.h b/include/wl_handle.h
new file mode 100644 (file)
index 0000000..645b411
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ *              William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_WL_PROXY_H_
+#define WLCS_WL_PROXY_H_
+
+#include "wl_interface_descriptor.h"
+#include "boost/throw_exception.hpp"
+#include "boost/current_function.hpp"
+
+#include <string>
+#include <stdexcept>
+
+struct wl_proxy;
+
+namespace wlcs
+{
+template<typename T>
+class WlHandle
+{
+public:
+    explicit WlHandle(T* const proxy)
+        : proxy{proxy},
+          owns_wl_object{true}
+    {
+        if (proxy == nullptr)
+        {
+            BOOST_THROW_EXCEPTION((std::logic_error{"Attempt to construct a WlHandle from null Wayland object"}));
+        }
+    }
+
+    ~WlHandle()
+    {
+        if (owns_wl_object)
+        {
+            (*WlInterfaceDescriptor<T>::destructor)(proxy);
+        }
+    }
+
+    WlHandle(WlHandle&& from)
+        : WlHandle(from.proxy)
+    {
+        from.owns_wl_object = false;
+    }
+
+    WlHandle(WlHandle const&) = delete;
+
+    auto operator=(WlHandle&&) -> WlHandle& = delete;
+    auto operator=(WlHandle const&) -> bool = delete;
+
+    operator T*() const
+    {
+        // This is a precondition failure, but as this is a test-suite let's be generous and make it fail hard and fast
+        if (!owns_wl_object)
+        {
+            std::abort();
+        }
+        return proxy;
+    }
+
+    auto wl_proxy() const -> struct wl_proxy*
+    {
+        // This is a precondition failure, but as this is a test-suite let's be generous and make it fail hard and fast
+        if (!owns_wl_object)
+        {
+            std::abort();
+        }
+        return static_cast<struct wl_proxy*>(proxy);
+    }
+
+private:
+    T* const proxy;
+    bool owns_wl_object;
+};
+
+template<typename WlType>
+auto wrap_wl_object(WlType* proxy) -> WlHandle<WlType>
+{
+    static_assert(
+        WlInterfaceDescriptor<WlType>::has_specialisation,
+        "Missing specialisation for WlInterfaceDescriptor");
+    return WlHandle<WlType>{proxy};
+}
+
+}
+
+#endif // WLCS_WL_PROXY_H_
diff --git a/include/wl_interface_descriptor.h b/include/wl_interface_descriptor.h
new file mode 100644 (file)
index 0000000..ae71a93
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_WL_INTERFACE_DESCRIPTOR_H
+#define WLCS_WL_INTERFACE_DESCRIPTOR_H
+
+struct wl_interface;
+
+namespace wlcs
+{
+/***
+ * A specialisable struct containing the constants and types associated with a Wayland protocol
+ *
+ * \tparam WlType   The base Wayland object type (eg; wl_surface, xdg_wm_base, etc)
+ */
+template<typename WlType>
+struct WlInterfaceDescriptor
+{
+    // Needed because apparently GCC < 10 can't compare a constexpr wl_interface const* const to nullptr in constexpr context?!
+    static constexpr bool const has_specialisation = false;
+    static constexpr wl_interface const* const interface = nullptr;
+    static constexpr void (* const destructor)(WlType*) = nullptr;
+};
+}
+
+/***
+ * Declare a specialisation of WlInterfaceDescriptor for \param name
+ *
+ * This will use the standard Wayland conventions of
+ * name - name_interface - name_destroy
+ * (eg: wl_surface - wl_surface_interface - wl_surface_destroy)
+ *
+ * If an interface requires special handling, a manual specialisation can be
+ * provided (for example, see wl_output handling in in_process_server.h)
+ */
+#define WLCS_CREATE_INTERFACE_DESCRIPTOR(name) \
+    template<> \
+    struct WlInterfaceDescriptor<name> \
+    { \
+        static constexpr bool const has_specialisation = true; \
+        static constexpr wl_interface const* const interface = &name##_interface; \
+        static constexpr void(* const destructor)(name*) = &name##_destroy; \
+    };
+
+
+#endif //WLCS_WL_INTERFACE_DESCRIPTOR_H
diff --git a/include/wlcs/display_server.h b/include/wlcs/display_server.h
new file mode 100644 (file)
index 0000000..0bb79dd
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+#ifndef WLCS_SERVER_H_
+#define WLCS_SERVER_H_
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct wl_surface wl_surface;
+typedef struct wl_display wl_display;
+typedef struct wl_event_loop wl_event_loop;
+
+typedef struct WlcsPointer WlcsPointer;
+typedef struct WlcsTouch WlcsTouch;
+
+/**
+ * Maximum version of WlcsIntegrationDescriptor this header provides a definition for
+ */
+#define WLCS_INTEGRATION_DESCRIPTOR_VERSION 1
+typedef struct WlcsExtensionDescriptor WlcsExtensionDescriptor;
+struct WlcsExtensionDescriptor
+{
+    /**
+     * Protocol name of extension (eg: wl_shell, xdg_shell, etc)
+     */
+    char const* name;
+    /**
+     * Maximum version of extension supported
+     */
+    uint32_t version;
+};
+
+typedef struct WlcsIntegrationDescriptor WlcsIntegrationDescriptor;
+struct WlcsIntegrationDescriptor
+{
+    uint32_t version; /**< Version of the struct this instance provides */
+
+    size_t num_extensions; /**< Length of the supported_extensions array */
+    /**
+     * Array of extension descriptions
+     *
+     * This must be an array of length num_extensions; that is, accesses from
+     * supported_extensions[0] to supported_extensions[num_extensions - 1]
+     * must be valid.
+     */
+    WlcsExtensionDescriptor const* supported_extensions;
+};
+
+/**
+ * Maximum version of WlcsDisplayServer this header provides a definition for
+ */
+#define WLCS_DISPLAY_SERVER_VERSION 3
+typedef struct WlcsDisplayServer WlcsDisplayServer;
+struct WlcsDisplayServer
+{
+    uint32_t version; /**< Version of the struct this instance provides */
+
+    /**
+     * Start the display server's mainloop.
+     *
+     * This should *not* block until the mainloop exits, which implies the mainloop
+     * will need to be run in a separate thread.
+     *
+     * This does not need to block until the display server is ready to process
+     * input, but the WlcsDisplayServer does need to be able to process other
+     * calls (notably create_client_socket) once this returns.
+     */
+    void (*start)(WlcsDisplayServer* server);
+
+    /**
+     * Stop the display server's mainloop.
+     *
+     * In contrast to the start hook, this *should* block until the server's mainloop
+     * has been torn down, so that it does not persist into later tests.
+     */
+    void (*stop)(WlcsDisplayServer* server);
+
+    /**
+     * Create a socket that can be connected to by wl_display_connect_fd
+     *
+     * \return  A FD to a client Wayland socket. WLCS owns this fd, and will close
+     *          it as necessary.
+     */
+    int (*create_client_socket)(WlcsDisplayServer* server);
+
+    /**
+     * Position a window in the compositor coördinate space
+     *
+     * \param client   the (wayland-client-side) wl_client which owns the
+     *                  surface
+     * \param surface  the (wayland-client-side) wl_surface*
+     * \param x         x coördinate (in compositor-space pixels) to move the
+     *                  left of the window to
+     * \param y         y coördinate (in compositor-space pixels) to move the
+     *                  top of the window to
+     */
+    void (*position_window_absolute)(WlcsDisplayServer* server, wl_display* client, wl_surface* surface, int x, int y);
+
+    /**
+     * Create a fake pointer device
+     */
+    WlcsPointer* (*create_pointer)(WlcsDisplayServer* server);
+
+    /**
+     * Create a mock touch object
+     */
+    WlcsTouch* (*create_touch)(WlcsDisplayServer* server);
+
+    /* Added in version 2 */
+    /**
+     * Describe the capabilities of this WlcsDisplayServer
+     *
+     * WLCS will use this description to skip tests that the display server
+     * is known to not support. For example, if the set of extensions
+     * described by the WlcsIntegrationDescriptor does not include "xdg_shell"
+     * then all XDG Shell tests will be skipped.
+     *
+     * Different WlcsDisplayServer instances may report different
+     * capabilities (for example, if command line options should influence
+     * the set of extensions exposed).
+     */
+    WlcsIntegrationDescriptor const* (*get_descriptor)(WlcsDisplayServer const* server);
+
+    /* Added in version 3 */
+    /**
+     * Start the display server's event loop, blocking the calling thread.
+     *
+     * When started in this way WLCS will proxy all requests to this mainloop.
+     * All calls to WLCS interfaces will be dispatched from the
+     * wlcs_event_dispatcher loop, so implementations are required to drive
+     * this loop from their own.
+     *
+     * \note    This is an optional interface. An implementation must provide at
+     *          least one of {start, start_on_this_thread}, but does not need to
+     *          provide both. If both are provided, start is preferred.
+
+     * \param wlcs_event_dispatcher
+     */
+    void (*start_on_this_thread)(WlcsDisplayServer* server, wl_event_loop* wlcs_event_dispatcher);
+};
+
+/**
+ * Maximum version of WlcsServerIntegration this header provides a definition of
+ */
+#define WLCS_SERVER_INTEGRATION_VERSION 1
+typedef struct WlcsServerIntegration WlcsServerIntegration;
+struct WlcsServerIntegration
+{
+    uint32_t version; /**< Version of the struct this instance provides */
+
+    /**
+     * Create a WlcsDisplayServer instance
+     *
+     * This can do any setup necessary, but should not start the compositor's
+     * mainloop.
+     *
+     * \param argc  Command line argument count (after wlcs-specific options
+     *              have been stripped)
+     * \param argv  Command line arguments (after wlcs-specific options have
+     *              been stripped)
+     * \return
+     */
+    WlcsDisplayServer* (*create_server)(int argc, char const** argv);
+    void (*destroy_server)(WlcsDisplayServer* server);
+};
+
+/**
+ * Main WLCS entry point
+ *
+ * WLCS will load this symbol to discover the compositor integration
+ * hooks.
+ */
+extern WlcsServerIntegration const wlcs_server_integration;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //WLCS_SERVER_H_
diff --git a/include/wlcs/pointer.h b/include/wlcs/pointer.h
new file mode 100644 (file)
index 0000000..0ee14d3
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+#ifndef WLCS_POINTER_H_
+#define WLCS_POINTER_H_
+
+#include <wayland-client-core.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Maximum version of WlcsPointer this header provides a definition for
+ */
+#define WLCS_POINTER_VERSION 1
+
+typedef struct WlcsPointer WlcsPointer;
+/**
+ * An object to manipulate the server's pointer state
+ */
+struct WlcsPointer
+{
+    uint32_t version; /**< Version of the struct this instance provides */
+
+    /**
+     * Move the pointer to the specified location, in compositor coördinate space
+     */
+    void (*move_absolute)(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y);
+    /**
+     * Move the pointer by the specified amount, in compositor coördinates.
+     */
+    void (*move_relative)(WlcsPointer* pointer, wl_fixed_t dx, wl_fixed_t dy);
+
+    /**
+     * Generate a button-up event
+     *
+     * \param button    Button code (as per wl_pointer. eg: BTN_LEFT)
+     */
+    void (*button_up)(WlcsPointer* pointer, int button);
+    /**
+     * Generate a button-down event
+     *
+     * \param button    Button code (as per wl_pointer. eg: BTN_LEFT)
+     */
+    void (*button_down)(WlcsPointer* pointer, int button);
+
+    /**
+     * Destroy this pointer, freeing any resources.
+     */
+    void (*destroy)(WlcsPointer* pointer);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //WLCS_SERVER_H_
diff --git a/include/wlcs/touch.h b/include/wlcs/touch.h
new file mode 100644 (file)
index 0000000..8dfe3e6
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+#ifndef WLCS_TOUCH_H_
+#define WLCS_TOUCH_H_
+
+#include <wayland-client-core.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Maximum version of WlcsTouch this header provides a definition for
+ */
+#define WLCS_TOUCH_VERSION 1
+
+typedef struct WlcsTouch WlcsTouch;
+struct WlcsTouch
+{
+    uint32_t version; /**< Version of the struct this instance provides */
+
+    void (*touch_down)(WlcsTouch* touch, wl_fixed_t x, wl_fixed_t y);
+    void (*touch_move)(WlcsTouch* touch, wl_fixed_t x, wl_fixed_t y);
+    void (*touch_up)(WlcsTouch* touch);
+
+    void (*destroy)(WlcsTouch* touch);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //WLCS_TOUCH_H_
diff --git a/include/xdg_output_v1.h b/include/xdg_output_v1.h
new file mode 100644 (file)
index 0000000..7d2d3c8
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef XDG_OUTPUT_V1_H
+#define XDG_OUTPUT_V1_H
+
+#include "generated/wayland-client.h"
+#include "generated/xdg-output-unstable-v1-client.h"
+
+#include "in_process_server.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+#include <memory>
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zxdg_output_manager_v1)
+
+class XdgOutputManagerV1
+{
+public:
+    XdgOutputManagerV1(Client& client);
+    ~XdgOutputManagerV1();
+
+    operator zxdg_output_manager_v1*() const;
+    auto client() const -> Client&;
+
+private:
+    struct Impl;
+    std::unique_ptr<Impl> const impl;
+};
+
+class XdgOutputV1
+{
+public:
+    struct State
+    {
+        std::experimental::optional<std::pair<int, int>> logical_position;
+        std::experimental::optional<std::pair<int, int>> logical_size;
+        std::experimental::optional<std::string> name;
+        std::experimental::optional<std::string> description;
+    };
+
+    XdgOutputV1(XdgOutputManagerV1& manager, size_t output_index);
+
+    XdgOutputV1(XdgOutputV1 const&) = delete;
+    XdgOutputV1 const& operator=(XdgOutputV1 const&) = delete;
+
+    operator zxdg_output_v1*() const;
+    auto state() -> State const&;
+
+private:
+    struct Impl;
+    std::shared_ptr<Impl> const impl; // shared instead of unique because interally a weak is needed
+};
+
+}
+
+#endif // XDG_OUTPUT_V1_H
diff --git a/include/xdg_shell_stable.h b/include/xdg_shell_stable.h
new file mode 100644 (file)
index 0000000..bd2b7e2
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_XDG_SHELL_STABLE_H
+#define WLCS_XDG_SHELL_STABLE_H
+
+#include "in_process_server.h"
+#include "generated/xdg-shell-client.h"
+#include "wl_interface_descriptor.h"
+#include "wl_handle.h"
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(xdg_wm_base)
+
+class XdgSurfaceStable
+{
+public:
+    XdgSurfaceStable(wlcs::Client& client, wlcs::Surface& surface);
+    XdgSurfaceStable(XdgSurfaceStable const&) = delete;
+    XdgSurfaceStable& operator=(XdgSurfaceStable const&) = delete;
+    ~XdgSurfaceStable();
+
+    void add_configure_notification(std::function<void(uint32_t)> notification)
+    {
+        configure_notifiers.push_back(notification);
+    }
+
+    operator xdg_surface*() const {return shell_surface;}
+
+    std::vector<std::function<void(uint32_t)>> configure_notifiers;
+
+private:
+    static void configure_thunk(void *data, struct xdg_surface *, uint32_t serial)
+    {
+        for (auto& notifier : static_cast<XdgSurfaceStable*>(data)->configure_notifiers)
+        {
+            notifier(serial);
+        }
+    }
+
+    xdg_surface* shell_surface;
+};
+
+class XdgToplevelStable
+{
+public:
+    struct State
+    {
+        State(int32_t width, int32_t height, struct wl_array *states);
+
+        int width;
+        int height;
+
+        bool maximized;
+        bool fullscreen;
+        bool resizing;
+        bool activated;
+    };
+
+    XdgToplevelStable(XdgSurfaceStable& shell_surface_);
+    XdgToplevelStable(XdgToplevelStable const&) = delete;
+    XdgToplevelStable& operator=(XdgToplevelStable const&) = delete;
+    ~XdgToplevelStable();
+
+    void add_configure_notification(std::function<void(int32_t, int32_t, struct wl_array *)> notification)
+    {
+        configure_notifiers.push_back(notification);
+    }
+
+    void add_close_notification(std::function<void()> notification)
+    {
+        close_notifiers.push_back(notification);
+    }
+
+    operator xdg_toplevel*() const {return toplevel;}
+
+    XdgSurfaceStable* const shell_surface;
+    xdg_toplevel* toplevel;
+
+    std::vector<std::function<void(int32_t, int32_t, struct wl_array *)>> configure_notifiers;
+    std::vector<std::function<void()>> close_notifiers;
+
+private:
+    static void configure_thunk(void *data, struct xdg_toplevel *, int32_t width, int32_t height,
+                                struct wl_array *states)
+    {
+        for (auto& notifier : static_cast<XdgToplevelStable*>(data)->configure_notifiers)
+        {
+            notifier(width, height, states);
+        }
+    }
+
+    static void close_thunk(void *data, struct xdg_toplevel *)
+    {
+        for (auto& notifier : static_cast<XdgToplevelStable*>(data)->close_notifiers)
+        {
+            notifier();
+        }
+    }
+};
+
+class XdgPositionerStable
+{
+public:
+    XdgPositionerStable(wlcs::Client& client);
+    ~XdgPositionerStable();
+    operator xdg_positioner*() const {return positioner;}
+
+private:
+    xdg_positioner* const positioner;
+};
+
+class XdgPopupStable
+{
+public:
+    XdgPopupStable(
+        XdgSurfaceStable& shell_surface_,
+        std::experimental::optional<XdgSurfaceStable*> parent,
+        XdgPositionerStable& positioner);
+    XdgPopupStable(XdgPopupStable const&) = delete;
+    XdgPopupStable& operator=(XdgPopupStable const&) = delete;
+    ~XdgPopupStable();
+
+    void add_configure_notification(std::function<void(int32_t, int32_t, int32_t, int32_t)> notification)
+    {
+        configure_notifiers.push_back(notification);
+    }
+
+    void add_close_notification(std::function<void()> notification)
+    {
+        popup_done_notifiers.push_back(notification);
+    }
+
+    operator xdg_popup*() const {return popup;}
+
+    XdgSurfaceStable* const shell_surface;
+    xdg_popup* const popup;
+
+    std::vector<std::function<void(int32_t, int32_t, int32_t, int32_t)>> configure_notifiers;
+    std::vector<std::function<void()>> popup_done_notifiers;
+
+private:
+    static void configure_thunk(void* data, struct xdg_popup*, int32_t x, int32_t y,
+                                int32_t width, int32_t height)
+    {
+        for (auto& notifier : static_cast<XdgPopupStable*>(data)->configure_notifiers)
+        {
+            notifier(x, y, width, height);
+        }
+    }
+
+    static void popup_done_thunk(void *data, struct xdg_popup*)
+    {
+        for (auto& notifier : static_cast<XdgPopupStable*>(data)->popup_done_notifiers)
+        {
+            notifier();
+        }
+    }
+};
+
+}
+
+#endif // WLCS_XDG_SHELL_STABLE_H
diff --git a/include/xdg_shell_v6.h b/include/xdg_shell_v6.h
new file mode 100644 (file)
index 0000000..73394f2
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#ifndef WLCS_XDG_SHELL_V6_
+#define WLCS_XDG_SHELL_V6_
+
+#include "in_process_server.h"
+#include "generated/xdg-shell-unstable-v6-client.h"
+
+namespace wlcs
+{
+
+class XdgSurfaceV6
+{
+public:
+    XdgSurfaceV6(wlcs::Client& client, wlcs::Surface& surface);
+    XdgSurfaceV6(XdgSurfaceV6 const&) = delete;
+    XdgSurfaceV6& operator=(XdgSurfaceV6 const&) = delete;
+    ~XdgSurfaceV6();
+
+    void add_configure_notification(std::function<void(uint32_t)> notification)
+    {
+        configure_notifiers.push_back(notification);
+    }
+
+    operator zxdg_surface_v6*() const {return shell_surface;}
+
+    std::vector<std::function<void(uint32_t)>> configure_notifiers;
+
+private:
+    static void configure_thunk(void *data, struct zxdg_surface_v6 *, uint32_t serial)
+    {
+        for (auto& notifier : static_cast<XdgSurfaceV6*>(data)->configure_notifiers)
+        {
+            notifier(serial);
+        }
+    }
+
+    zxdg_surface_v6* shell_surface;
+};
+
+class XdgToplevelV6
+{
+public:
+    struct State
+    {
+        State(int32_t width, int32_t height, struct wl_array *states);
+
+        int width;
+        int height;
+
+        bool maximized;
+        bool fullscreen;
+        bool resizing;
+        bool activated;
+    };
+
+    XdgToplevelV6(XdgSurfaceV6& shell_surface_);
+    XdgToplevelV6(XdgToplevelV6 const&) = delete;
+    XdgToplevelV6& operator=(XdgToplevelV6 const&) = delete;
+    ~XdgToplevelV6();
+
+    void add_configure_notification(std::function<void(int32_t, int32_t, struct wl_array *)> notification)
+    {
+        configure_notifiers.push_back(notification);
+    }
+
+    void add_close_notification(std::function<void()> notification)
+    {
+        close_notifiers.push_back(notification);
+    }
+
+    operator zxdg_toplevel_v6*() const {return toplevel;}
+
+    XdgSurfaceV6* const shell_surface;
+    zxdg_toplevel_v6* toplevel;
+
+    std::vector<std::function<void(int32_t, int32_t, struct wl_array *)>> configure_notifiers;
+    std::vector<std::function<void()>> close_notifiers;
+
+private:
+    static void configure_thunk(void *data, struct zxdg_toplevel_v6 *, int32_t width, int32_t height,
+                                struct wl_array *states)
+    {
+        for (auto& notifier : static_cast<XdgToplevelV6*>(data)->configure_notifiers)
+        {
+            notifier(width, height, states);
+        }
+    }
+
+    static void close_thunk(void *data, struct zxdg_toplevel_v6 *)
+    {
+        for (auto& notifier : static_cast<XdgToplevelV6*>(data)->close_notifiers)
+        {
+            notifier();
+        }
+    }
+};
+
+class XdgPositionerV6
+{
+public:
+    XdgPositionerV6(wlcs::Client& client);
+    ~XdgPositionerV6();
+    operator zxdg_positioner_v6*() const {return positioner;}
+
+private:
+    zxdg_positioner_v6* const positioner;
+};
+
+class XdgPopupV6
+{
+public:
+    XdgPopupV6(XdgSurfaceV6& shell_surface_, XdgSurfaceV6& parent, XdgPositionerV6& positioner);
+    XdgPopupV6(XdgPopupV6 const&) = delete;
+    XdgPopupV6& operator=(XdgPopupV6 const&) = delete;
+    ~XdgPopupV6();
+
+    void add_configure_notification(std::function<void(int32_t, int32_t, int32_t, int32_t)> notification)
+    {
+        configure_notifiers.push_back(notification);
+    }
+
+    void add_close_notification(std::function<void()> notification)
+    {
+        popup_done_notifiers.push_back(notification);
+    }
+
+    operator zxdg_popup_v6*() const {return popup;}
+
+    XdgSurfaceV6* const shell_surface;
+    zxdg_popup_v6* const popup;
+
+    std::vector<std::function<void(int32_t, int32_t, int32_t, int32_t)>> configure_notifiers;
+    std::vector<std::function<void()>> popup_done_notifiers;
+
+private:
+    static void configure_thunk(void *data, struct zxdg_popup_v6 *, int32_t x, int32_t y,
+                                int32_t width, int32_t height)
+    {
+        for (auto& notifier : static_cast<XdgPopupV6*>(data)->configure_notifiers)
+        {
+            notifier(x, y, width, height);
+        }
+    }
+
+    static void popup_done_thunk(void *data, struct zxdg_popup_v6 *)
+    {
+        for (auto& notifier : static_cast<XdgPopupV6*>(data)->popup_done_notifiers)
+        {
+            notifier();
+        }
+    }
+};
+
+}
+
+#endif // WLCS_XDG_SHELL_V6_
diff --git a/spread.yaml b/spread.yaml
new file mode 100644 (file)
index 0000000..234c33f
--- /dev/null
@@ -0,0 +1,29 @@
+project: wlcs
+
+kill-timeout: 50m
+
+backends:
+    lxd:
+        systems:
+            - ubuntu-18.04
+            - ubuntu-20.04
+            - ubuntu-devel:
+                image: ubuntu-daily:devel/amd64
+            - fedora-31
+            - fedora-32
+            - alpine-3.11
+
+suites:
+    spread/build/:
+        summary: Build wlcs
+        environment:
+            CC/gcc: gcc
+            CXX/gcc: g++
+            CC/clang: clang
+            CXX/clang: clang++
+
+path:
+    /spread/wlcs
+
+exclude:
+    - .git
diff --git a/spread/build/alpine/task.yaml b/spread/build/alpine/task.yaml
new file mode 100644 (file)
index 0000000..d5e5561
--- /dev/null
@@ -0,0 +1,22 @@
+summary: Build (on Alpine Linux)
+systems: [alpine-*]
+
+execute: |
+    apk add \
+        coreutils \
+        make \
+        g++ \
+        wayland-dev \
+        cmake \
+        boost-dev \
+        gtest-dev
+
+    cd $SPREAD_PATH
+    cd $(mktemp --directory)
+    # Alpine doesn't build gcc's libsanitizer
+    cmake $SPREAD_PATH \
+        -DWLCS_BUILD_ASAN=False \
+        -DWLCS_BUILD_TSAN=False \
+        -DWLCS_BUILD_UBSAN=False
+    make -j$(nproc)
+
diff --git a/spread/build/fedora/task.yaml b/spread/build/fedora/task.yaml
new file mode 100644 (file)
index 0000000..065c796
--- /dev/null
@@ -0,0 +1,23 @@
+summary: Build (on Fedora)
+systems: [fedora-*]
+
+execute: |
+    dnf install --assumeyes \
+        wayland-devel \
+        cmake \
+        make \
+        clang \
+        gcc-c++ \
+        libasan.x86_64 \
+        libtsan.x86_64 \
+        libubsan.x86_64 \
+        pkg-config \
+        boost-devel \
+        gtest-devel \
+        gmock-devel
+
+    cd $SPREAD_PATH
+    cd $(mktemp --directory)
+    cmake $SPREAD_PATH
+    make -j$(nproc)
+
diff --git a/spread/build/ubuntu/task.yaml b/spread/build/ubuntu/task.yaml
new file mode 100644 (file)
index 0000000..ad45894
--- /dev/null
@@ -0,0 +1,34 @@
+summary: Build (on Ubuntu)
+systems: [-fedora-*, -alpine-*]
+
+execute: |
+    # Grab builds of Mir git master
+    add-apt-repository ppa:mir-team/dev
+
+    apt-get update
+
+    apt install --yes \
+        dpkg-dev \
+        libwayland-dev \
+        cmake \
+        clang \
+        g++ \
+        pkg-config \
+        libgtest-dev \
+        google-mock \
+        libboost-dev \
+        mir-test-tools
+
+    # Check that we build…
+    cd $SPREAD_PATH
+    cd $(mktemp --directory)
+    cmake -DCMAKE_C_FLAGS="-D_FORTIFY_SOURCE=2" -DCMAKE_CXX_FLAGS="-D_FORTIFY_SOURCE=2" $SPREAD_PATH
+    make -j$(nproc)
+
+    # …and run the Mir tests, but don't fail on them, unless we fail with a signal
+    # TODO: Store the set of passing tests in the Travis cache, and fail if a test which
+    #       previously passed now fails.
+    ./wlcs /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/mir/miral_wlcs_integration.so || {
+        test $? -lt 128 -o $? -gt 165
+    }
+
diff --git a/src/data_device.cpp b/src/data_device.cpp
new file mode 100644 (file)
index 0000000..966dc3d
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "data_device.h"
+
+wlcs::ActiveListeners wlcs::DataDeviceListener::active_listeners;
+constexpr wl_data_device_listener wlcs::DataDeviceListener::thunks;
+
+
+void wlcs::DataDeviceListener::data_offer(void* data, struct wl_data_device* wl_data_device, struct wl_data_offer* id)
+{
+    if (active_listeners.includes(data))
+        static_cast<DataDeviceListener*>(data)->data_offer(wl_data_device, id);
+}
+
+void wlcs::DataDeviceListener::enter(
+    void* data,
+    struct wl_data_device* wl_data_device,
+    uint32_t serial,
+    struct wl_surface* surface,
+    wl_fixed_t x,
+    wl_fixed_t y,
+    struct wl_data_offer* id)
+{
+    if (active_listeners.includes(data))
+        static_cast<DataDeviceListener*>(data)->enter(wl_data_device, serial, surface, x, y, id);
+}
+
+void wlcs::DataDeviceListener::leave(void* data, struct wl_data_device* wl_data_device)
+{
+    if (active_listeners.includes(data))
+        static_cast<DataDeviceListener*>(data)->leave(wl_data_device);
+}
+
+void wlcs::DataDeviceListener::motion(
+    void* data,
+    struct wl_data_device* wl_data_device,
+    uint32_t time,
+    wl_fixed_t x,
+    wl_fixed_t y)
+{
+    if (active_listeners.includes(data))
+        static_cast<DataDeviceListener*>(data)->motion(wl_data_device, time, x, y);
+}
+
+void wlcs::DataDeviceListener::drop(void* data, struct wl_data_device* wl_data_device)
+{
+    if (active_listeners.includes(data))
+        static_cast<DataDeviceListener*>(data)->drop(wl_data_device);
+}
+
+void wlcs::DataDeviceListener::selection(
+    void* data,
+    struct wl_data_device* wl_data_device,
+    struct wl_data_offer* id)
+{
+    if (active_listeners.includes(data))
+        static_cast<DataDeviceListener*>(data)->selection(wl_data_device, wl_data_device, id);
+}
+
+void wlcs::DataDeviceListener::data_offer(struct wl_data_device* /*wl_data_device*/, struct wl_data_offer* /*id*/)
+{
+}
+
+void wlcs::DataDeviceListener::enter(
+    struct wl_data_device* /*wl_data_device*/,
+    uint32_t /*serial*/,
+    struct wl_surface* /*surface*/,
+    wl_fixed_t /*x*/,
+    wl_fixed_t /*y*/,
+    struct wl_data_offer* /*id*/)
+{
+}
+
+void wlcs::DataDeviceListener::leave(struct wl_data_device* /*wl_data_device*/)
+{
+}
+
+void wlcs::DataDeviceListener::motion(
+    struct wl_data_device* /*wl_data_device*/,
+    uint32_t /*time*/,
+    wl_fixed_t /*x*/,
+    wl_fixed_t /*y*/)
+{
+}
+
+void wlcs::DataDeviceListener::drop(struct wl_data_device* /*wl_data_device*/)
+{
+}
+
+void wlcs::DataDeviceListener::selection(
+    struct wl_data_device* /*wl_data_device*/,
+    struct wl_data_offer* /*id*/)
+{
+}
+
+
+wlcs::ActiveListeners wlcs::DataOfferListener::active_listeners;
+constexpr wl_data_offer_listener wlcs::DataOfferListener::thunks;
+
+void wlcs::DataOfferListener::offer(void* data, struct wl_data_offer* data_offer, char const* mime_type)
+{
+    if (active_listeners.includes(data))
+        static_cast<DataOfferListener*>(data)->offer(data_offer, mime_type);
+}
+
+void wlcs::DataOfferListener::source_actions(void* data, struct wl_data_offer* data_offer, uint32_t dnd_actions)
+{
+    if (active_listeners.includes(data))
+        static_cast<DataOfferListener*>(data)->source_actions(data_offer, dnd_actions);
+}
+
+void wlcs::DataOfferListener::action(void* data, struct wl_data_offer* data_offer, uint32_t dnd_action)
+{
+    if (active_listeners.includes(data))
+        static_cast<DataOfferListener*>(data)->action(data_offer, dnd_action);
+}
+
+void wlcs::DataOfferListener::offer(struct wl_data_offer* /*data_offer*/, char const* /*mime_type*/)
+{
+}
+
+void wlcs::DataOfferListener::source_actions(struct wl_data_offer* /*data_offer*/, uint32_t /*dnd_actions*/)
+{
+}
+
+void wlcs::DataOfferListener::action(struct wl_data_offer* /*data_offer*/, uint32_t /*dnd_action*/)
+{
+}
diff --git a/src/gtk_primary_selection.cpp b/src/gtk_primary_selection.cpp
new file mode 100644 (file)
index 0000000..d39e9bf
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "gtk_primary_selection.h"
+
+#include <unistd.h>
+
+wlcs::ActiveListeners wlcs::GtkPrimarySelectionDeviceListener::active_listeners;
+constexpr gtk_primary_selection_device_listener wlcs::GtkPrimarySelectionDeviceListener::thunks;
+
+wlcs::ActiveListeners wlcs::GtkPrimarySelectionOfferListener::active_listeners;
+constexpr gtk_primary_selection_offer_listener wlcs::GtkPrimarySelectionOfferListener::thunks;
+
+wlcs::ActiveListeners wlcs::GtkPrimarySelectionSourceListener::active_listeners;
+constexpr gtk_primary_selection_source_listener wlcs::GtkPrimarySelectionSourceListener::thunks;
+
+
+void wlcs::GtkPrimarySelectionDeviceListener::data_offer(
+        void* data,
+        gtk_primary_selection_device* device,
+        gtk_primary_selection_offer* offer)
+{
+    if (active_listeners.includes(data))
+        static_cast<GtkPrimarySelectionDeviceListener*>(data)->data_offer(device, offer);
+}
+
+void wlcs::GtkPrimarySelectionDeviceListener::data_offer(gtk_primary_selection_device*, gtk_primary_selection_offer*)
+{
+}
+
+void wlcs::GtkPrimarySelectionDeviceListener::selection(gtk_primary_selection_device*, gtk_primary_selection_offer*)
+{
+}
+
+void wlcs::GtkPrimarySelectionDeviceListener::selection(
+        void* data,
+        gtk_primary_selection_device* device,
+        gtk_primary_selection_offer* offer)
+{
+    if (active_listeners.includes(data))
+        static_cast<GtkPrimarySelectionDeviceListener*>(data)->selection(device, offer);
+}
+
+void wlcs::GtkPrimarySelectionOfferListener::offer(gtk_primary_selection_offer*, const char*)
+{
+}
+
+void wlcs::GtkPrimarySelectionOfferListener::offer(
+        void* data, gtk_primary_selection_offer* offer, const char* mime_type)
+{
+    if (active_listeners.includes(data))
+        static_cast<GtkPrimarySelectionOfferListener*>(data)->offer(offer, mime_type);
+}
+
+void wlcs::GtkPrimarySelectionSourceListener::send(gtk_primary_selection_source*, const char*, int32_t fd)
+{
+    close(fd);
+}
+
+void wlcs::GtkPrimarySelectionSourceListener::cancelled(gtk_primary_selection_source*)
+{
+}
+
+void wlcs::GtkPrimarySelectionSourceListener::send(
+        void* data, gtk_primary_selection_source* source, const char* mime_type, int32_t fd)
+{
+    if (active_listeners.includes(data))
+        static_cast<GtkPrimarySelectionSourceListener*>(data)->send(source, mime_type, fd);
+}
+
+void wlcs::GtkPrimarySelectionSourceListener::cancelled(void* data, gtk_primary_selection_source* source)
+{
+    if (active_listeners.includes(data))
+        static_cast<GtkPrimarySelectionSourceListener*>(data)->cancelled(source);
+}
diff --git a/src/helpers.cpp b/src/helpers.cpp
new file mode 100644 (file)
index 0000000..7dc4f26
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright © 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2 or 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by:
+ *   Alexandros Frantzis <alexandros.frantzis@canonical.com>
+ */
+
+#include "helpers.h"
+#include "shared_library.h"
+
+#include <boost/throw_exception.hpp>
+#include <system_error>
+
+#include <fcntl.h>
+#include <linux/memfd.h>
+#include <sys/syscall.h>
+
+namespace
+{
+
+bool error_indicates_tmpfile_not_supported(int error)
+{
+    return
+        error == EISDIR ||  // Directory exists, but no support for O_TMPFILE
+        error == ENOENT ||  // Directory doesn't exist, and no support for O_TMPFILE
+        error == EOPNOTSUPP ||    // Filesystem that directory resides on does not support O_TMPFILE
+        error == EINVAL;    // There apparently exists at least one development board that has a kernel
+    // that incorrectly returns EINVAL. Yay.
+}
+
+int memfd_create(char const* name, unsigned int flags)
+{
+    return static_cast<int>(syscall(SYS_memfd_create, name, flags));
+}
+}
+
+int wlcs::helpers::create_anonymous_file(size_t size)
+{
+
+    int fd = memfd_create("wlcs-unnamed", MFD_CLOEXEC);
+    if (fd == -1 && errno == ENOSYS)
+    {
+        fd = open("/dev/shm", O_TMPFILE | O_RDWR | O_EXCL | O_CLOEXEC, S_IRWXU);
+
+        // Workaround for filesystems that don't support O_TMPFILE
+        if (fd == -1 && error_indicates_tmpfile_not_supported(errno))
+        {
+            char template_filename[] = "/dev/shm/wlcs-buffer-XXXXXX";
+            fd = mkostemp(template_filename, O_CLOEXEC);
+            if (fd != -1)
+            {
+                if (unlink(template_filename) < 0)
+                {
+                    close(fd);
+                    fd = -1;
+                }
+            }
+        }
+    }
+
+    if (fd == -1)
+    {
+        BOOST_THROW_EXCEPTION(
+            std::system_error(errno, std::system_category(), "Failed to open temporary file"));
+    }
+
+    if (ftruncate(fd, size) == -1)
+    {
+        close(fd);
+        BOOST_THROW_EXCEPTION(
+            std::system_error(errno, std::system_category(), "Failed to resize temporary file"));
+    }
+
+    return fd;
+}
+
+namespace
+{
+static int argc;
+static char const** argv;
+
+std::shared_ptr<WlcsServerIntegration const> entry_point;
+}
+
+void wlcs::helpers::set_command_line(int argc, char const** argv)
+{
+    ::argc = argc;
+    ::argv = argv;
+}
+
+int wlcs::helpers::get_argc()
+{
+    return ::argc;
+}
+
+char const** wlcs::helpers::get_argv()
+{
+    return ::argv;
+}
+
+void wlcs::helpers::set_entry_point(std::shared_ptr<WlcsServerIntegration const> const& entry_point)
+{
+    ::entry_point = entry_point;
+}
+
+std::shared_ptr<WlcsServerIntegration const> wlcs::helpers::get_test_hooks()
+{
+    return ::entry_point;
+}
diff --git a/src/in_process_server.cpp b/src/in_process_server.cpp
new file mode 100644 (file)
index 0000000..3550cfe
--- /dev/null
@@ -0,0 +1,1940 @@
+/*
+ * Copyright © 2017-2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include "in_process_server.h"
+#include "thread_proxy.h"
+#include "version_specifier.h"
+#include "wlcs/display_server.h"
+#include "helpers.h"
+#include "wlcs/pointer.h"
+#include "wlcs/touch.h"
+#include "xdg_shell_v6.h"
+#include "xdg_shell_stable.h"
+#include "generated/wayland-client.h"
+#include "generated/xdg-shell-unstable-v6-client.h"
+#include "generated/xdg-shell-client.h"
+
+#include "linux/input.h"
+#include <boost/throw_exception.hpp>
+#include <stdexcept>
+#include <memory>
+#include <vector>
+#include <algorithm>
+#include <experimental/optional>
+#include <map>
+#include <unordered_map>
+#include <chrono>
+#include <poll.h>
+
+using namespace std::literals::chrono_literals;
+
+class ShimNotImplemented : public std::logic_error
+{
+public:
+    ShimNotImplemented() : std::logic_error("Function not implemented in display server shim")
+    {
+    }
+
+    ShimNotImplemented(const std::string& name)
+        : std::logic_error("Function '" + name + "()' not implemented in display server shim")
+    {
+    }
+};
+
+wlcs::ExtensionExpectedlyNotSupported::ExtensionExpectedlyNotSupported(char const* extension, VersionSpecifier const& version)
+    : std::runtime_error{
+        std::string{"Extension: "} +
+        extension + " version " + version.describe() +
+        " not supported by compositor under test."}
+{
+    auto const skip_reason =
+        std::string{"Missing extension: "} + extension + version.describe();
+    ::testing::Test::RecordProperty("wlcs-skip-test", skip_reason);
+}
+
+wlcs::Timeout::Timeout(char const* message)
+    : std::runtime_error(message)
+{
+}
+
+class wlcs::Pointer::Impl
+{
+public:
+    template<typename Proxy>
+    Impl(
+        WlcsPointer* raw_device,
+        std::shared_ptr<Proxy> const& proxy,
+        std::shared_ptr<void const> const& keep_dso_loaded)
+        : keep_dso_loaded{keep_dso_loaded},
+          pointer{raw_device}
+    {
+        setup_thunks(proxy);
+    }
+
+    ~Impl()
+    {
+        destroy_thunk();
+    }
+
+    void move_to(int x, int y)
+    {
+        move_absolute_thunk(wl_fixed_from_int(x), wl_fixed_from_int(y));
+    }
+
+    void move_by(int dx, int dy)
+    {
+        move_relative_thunk(wl_fixed_from_int(dx), wl_fixed_from_int(dy));
+    }
+
+    void button_down(int button)
+    {
+        button_down_thunk(button);
+    }
+
+    void button_up(int button)
+    {
+        button_up_thunk(button);
+    }
+
+private:
+    template<typename Proxy>
+    void setup_thunks(std::shared_ptr<Proxy> const& proxy)
+    {
+        move_absolute_thunk = proxy->register_op(
+            [this](wl_fixed_t x, wl_fixed_t y)
+            {
+                pointer->move_absolute(pointer, x, y);
+            });
+        move_relative_thunk = proxy->register_op(
+            [this](wl_fixed_t dx, wl_fixed_t dy)
+            {
+                pointer->move_relative(pointer, dx, dy);
+            });
+        button_down_thunk = proxy->register_op(
+            [this](int button)
+            {
+                pointer->button_down(pointer, button);
+            });
+        button_up_thunk = proxy->register_op(
+            [this](int button)
+            {
+                pointer->button_up(pointer, button);
+            });
+        destroy_thunk = proxy->register_op(
+            [this]()
+            {
+                pointer->destroy(pointer);
+            });
+    }
+
+    std::shared_ptr<void const> const keep_dso_loaded;
+    WlcsPointer* const pointer;
+
+    std::function<void(wl_fixed_t, wl_fixed_t)> move_absolute_thunk;
+    std::function<void(wl_fixed_t, wl_fixed_t)> move_relative_thunk;
+    std::function<void(int)> button_down_thunk;
+    std::function<void(int)> button_up_thunk;
+    std::function<void()> destroy_thunk;
+};
+
+wlcs::Pointer::~Pointer() = default;
+wlcs::Pointer::Pointer(Pointer&&) = default;
+
+template<typename Proxy>
+wlcs::Pointer::Pointer(
+    WlcsPointer* raw_device,
+    std::shared_ptr<Proxy> const& proxy,
+    std::shared_ptr<void const> const& keep_dso_loaded)
+    : impl{std::make_unique<Impl>(raw_device, proxy, keep_dso_loaded)}
+{
+}
+
+void wlcs::Pointer::move_to(int x, int y)
+{
+    impl->move_to(x, y);
+}
+
+void wlcs::Pointer::move_by(int dx, int dy)
+{
+    impl->move_by(dx, dy);
+}
+
+void wlcs::Pointer::button_down(int button)
+{
+    impl->button_down(button);
+}
+
+void wlcs::Pointer::button_up(int button)
+{
+    impl->button_up(button);
+}
+
+void wlcs::Pointer::click(int button)
+{
+    button_down(button);
+    button_up(button);
+}
+
+void wlcs::Pointer::left_button_down()
+{
+    button_down(BTN_LEFT);
+}
+
+void wlcs::Pointer::left_button_up()
+{
+    button_up(BTN_LEFT);
+}
+
+void wlcs::Pointer::left_click()
+{
+    click(BTN_LEFT);
+}
+
+class wlcs::Touch::Impl
+{
+public:
+    template<typename Proxy>
+    Impl(
+        WlcsTouch* raw_device,
+        std::shared_ptr<Proxy> const& proxy,
+        std::shared_ptr<void const> const& keep_dso_loaded)
+        : keep_dso_loaded{keep_dso_loaded},
+          touch{raw_device, proxy->register_op([](WlcsTouch* raw_device) { raw_device->destroy(raw_device); })}
+    {
+        if (touch->version != WLCS_TOUCH_VERSION)
+        {
+            BOOST_THROW_EXCEPTION((
+                std::runtime_error{
+                    std::string{"Unexpected WlcsTouch version. Expected: "} +
+                    std::to_string(WLCS_TOUCH_VERSION) +
+                    " received: " +
+                    std::to_string(touch->version)}));
+        }
+        set_up_thunks(proxy);
+    }
+
+    void down_at(int x, int y)
+    {
+        touch->touch_down(touch.get(), x, y);
+    }
+
+    void move_to(int x, int y)
+    {
+        touch->touch_move(touch.get(), x, y);
+    }
+
+    void up()
+    {
+        touch->touch_up(touch.get());
+    }
+
+private:
+    template<typename Proxy>
+    void set_up_thunks(std::shared_ptr<Proxy> const& proxy)
+    {
+        touch_down_thunk = proxy->register_op(
+            [this](int x, int y)
+            {
+                touch->touch_down(touch.get(), x, y);
+            });
+        touch_move_thunk = proxy->register_op(
+            [this](int x, int y)
+            {
+                touch->touch_move(touch.get(), x, y);
+            });
+        touch_up_thunk = proxy->register_op(
+            [this]()
+            {
+                touch->touch_up(touch.get());
+            });
+    }
+
+    std::shared_ptr<void const> const keep_dso_loaded;
+    std::unique_ptr<WlcsTouch, std::function<void(WlcsTouch*)>> const touch;
+
+    std::function<void(int, int)> touch_down_thunk;
+    std::function<void(int, int)> touch_move_thunk;
+    std::function<void()> touch_up_thunk;
+};
+
+wlcs::Touch::~Touch() = default;
+wlcs::Touch::Touch(Touch&&) = default;
+
+template<typename Proxy>
+wlcs::Touch::Touch(
+    WlcsTouch* raw_device,
+    std::shared_ptr<Proxy> const& proxy,
+    std::shared_ptr<void const> const& keep_dso_loaded)
+    : impl{std::make_unique<Impl>(raw_device, proxy, keep_dso_loaded)}
+{
+}
+
+void wlcs::Touch::down_at(int x, int y)
+{
+    impl->down_at(x, y);
+}
+
+void wlcs::Touch::move_to(int x, int y)
+{
+    impl->move_to(x, y);
+}
+
+void wlcs::Touch::up()
+{
+    impl->up();
+}
+
+namespace
+{
+std::shared_ptr<std::unordered_map<std::string, uint32_t> const> extract_supported_extensions(WlcsDisplayServer* server)
+{
+    if (server->version < 2)
+    {
+        return {};
+    }
+
+    auto const descriptor = server->get_descriptor(server);
+    auto extensions = std::make_shared<std::unordered_map<std::string, uint32_t>>();
+
+    for (auto i = 0u; i < descriptor->num_extensions; ++i)
+    {
+        (*extensions)[descriptor->supported_extensions[i].name] = descriptor->supported_extensions[i].version;
+    }
+    return extensions;
+}
+
+class NullProxy
+{
+public:
+    template<typename Callable>
+    auto register_op(Callable handler)
+    {
+        return handler;
+    }
+};
+}
+
+class wlcs::Server::Impl
+{
+public:
+    Impl(std::shared_ptr<WlcsServerIntegration const> const& hooks, int argc, char const** argv)
+        : server{hooks->create_server(argc, argv), hooks->destroy_server},
+          thread_context{make_context_if_needed(*server)},
+          hooks{hooks},
+          supported_extensions_{extract_supported_extensions(server.get())}
+    {
+        if (hooks->version < 1)
+        {
+            BOOST_THROW_EXCEPTION((std::runtime_error{"Server integration too old"}));
+        }
+        if (server->version < 1)
+        {
+            BOOST_THROW_EXCEPTION((std::runtime_error{"Server integration too old"}));
+        }
+        if (!server->stop)
+        {
+            BOOST_THROW_EXCEPTION((std::logic_error{"Missing required WlcsDisplayServer.stop definition"}));
+        }
+        if (thread_context)
+        {
+            initialise_thunks(thread_context->proxy);
+        }
+        else
+        {
+            initialise_thunks(std::make_shared<NullProxy>());
+        }
+    }
+
+    void start()
+    {
+        if (thread_context)
+        {
+            thread_context->server_thread = std::thread{
+                [this]()
+                {
+                    server->start_on_this_thread(server.get(), thread_context->event_loop.get());
+                }};
+        }
+        else
+        {
+            server->start(server.get());
+        }
+    }
+
+    void stop()
+    {
+        stop_thunk();
+    }
+
+    int create_client_socket()
+    {
+        auto fd = create_client_socket_thunk();
+        if (fd < 0)
+        {
+            BOOST_THROW_EXCEPTION((std::system_error{
+                errno,
+                std::system_category(),
+                "Failed to get client socket from server"}));
+        }
+        return fd;
+    }
+
+    Pointer create_pointer()
+    {
+        if (thread_context)
+        {
+            return Pointer{create_pointer_thunk(), thread_context->proxy, hooks};
+        }
+        else
+        {
+            return Pointer{create_pointer_thunk(), std::make_shared<NullProxy>(), hooks};
+        }
+    }
+
+    Touch create_touch()
+    {
+        if (thread_context)
+        {
+            return Touch{create_touch_thunk(), thread_context->proxy, hooks};
+        }
+        else
+        {
+            return Touch{create_touch_thunk(), std::make_shared<NullProxy>(), hooks};
+        }
+    }
+
+    void move_surface_to(Surface& surface, int x, int y)
+    {
+        // Ensure the server knows about the IDs we're about to send...
+        surface.owner().roundtrip();
+
+        position_window_absolute_thunk(surface.owner(), surface, x, y);
+    }
+
+    WlcsDisplayServer* wlcs_server() const
+    {
+        return server.get();
+    }
+
+    std::shared_ptr<const std::unordered_map<std::string, uint32_t>> supported_extensions() const
+    {
+        return supported_extensions_;
+    }
+
+private:
+    struct ThreadContext
+    {
+        explicit ThreadContext(std::unique_ptr<struct wl_event_loop, decltype(&wl_event_loop_destroy)> loop)
+            : event_loop{std::move(loop)},
+              proxy{std::make_shared<ThreadProxy>(event_loop.get())}
+        {
+        }
+        ThreadContext(ThreadContext&&) = default;
+
+        ~ThreadContext()
+        {
+            if (server_thread.joinable())
+            {
+                server_thread.join();
+            }
+        }
+
+        std::unique_ptr<struct wl_event_loop, void(*)(struct wl_event_loop*)> event_loop;
+        std::thread server_thread;
+        std::shared_ptr<ThreadProxy> proxy;
+    };
+
+    static std::experimental::optional<ThreadContext> make_context_if_needed(WlcsDisplayServer const& server)
+    {
+        if (server.version >= 3)
+        {
+            if (!server.start)
+            {
+                if (!server.start_on_this_thread)
+                {
+                    BOOST_THROW_EXCEPTION((
+                        std::runtime_error{"Server integration missing both start() and start_on_this_thread()"}));
+                }
+                auto loop = std::unique_ptr<struct wl_event_loop, decltype(&wl_event_loop_destroy)>{
+                    wl_event_loop_create(),
+                    &wl_event_loop_destroy
+                };
+                if (!loop)
+                {
+                    BOOST_THROW_EXCEPTION((
+                        std::runtime_error{"Failed to create eventloop for WLCS events"}));
+                }
+
+                return {ThreadContext{std::move(loop)}};
+            }
+        }
+        return {};
+    };
+
+    std::unique_ptr<WlcsDisplayServer, void(*)(WlcsDisplayServer*)> const server;
+    std::experimental::optional<ThreadContext> thread_context;
+    std::shared_ptr<WlcsServerIntegration const> const hooks;
+    std::shared_ptr<std::unordered_map<std::string, uint32_t> const> const supported_extensions_;
+
+    template<typename Proxy>
+    void initialise_thunks(std::shared_ptr<Proxy> proxy)
+    {
+        stop_thunk = proxy->register_op(
+            [this]()
+            {
+                server->stop(server.get());
+            });
+        create_client_socket_thunk = proxy->register_op(
+            [this]()
+            {
+                return server->create_client_socket(server.get());
+            });
+        create_pointer_thunk = proxy->register_op(
+            [this]()
+            {
+                return server->create_pointer(server.get());
+            });
+        create_touch_thunk = proxy->register_op(
+            [this]()
+            {
+                return server->create_touch(server.get());
+            });
+        position_window_absolute_thunk = proxy->register_op(
+            [this](
+                struct wl_display* client,
+                struct wl_surface* surface,
+                int x,
+                int y)
+            {
+                server->position_window_absolute(
+                    server.get(),
+                    client,
+                    surface,
+                    x,
+                    y);
+            });
+    }
+    std::function<void()> stop_thunk;
+    std::function<int()> create_client_socket_thunk;
+    std::function<WlcsPointer*()> create_pointer_thunk;
+    std::function<WlcsTouch*()> create_touch_thunk;
+    std::function<void(struct wl_display*, struct wl_surface*, int, int)> position_window_absolute_thunk;
+};
+
+wlcs::Server::Server(
+    std::shared_ptr<WlcsServerIntegration const> const& hooks,
+    int argc,
+    char const** argv)
+    : impl{std::make_unique<wlcs::Server::Impl>(hooks, argc, argv)}
+{
+}
+
+wlcs::Server::~Server() = default;
+
+void wlcs::Server::start()
+{
+    impl->start();
+}
+
+void wlcs::Server::stop()
+{
+    impl->stop();
+}
+
+std::shared_ptr<const std::unordered_map<std::string, uint32_t>> wlcs::Server::supported_extensions()
+{
+    return impl->supported_extensions();
+}
+
+int wlcs::Server::create_client_socket()
+{
+    return impl->create_client_socket();
+}
+
+void wlcs::Server::move_surface_to(Surface& surface, int x, int y)
+{
+    impl->move_surface_to(surface, x, y);
+}
+
+wlcs::Pointer wlcs::Server::create_pointer()
+{
+    return impl->create_pointer();
+}
+
+wlcs::Touch wlcs::Server::create_touch()
+{
+    return impl->create_touch();
+}
+
+wlcs::InProcessServer::InProcessServer()
+    : server{helpers::get_test_hooks(), helpers::get_argc(), helpers::get_argv()}
+{
+}
+
+void wlcs::InProcessServer::SetUp()
+{
+    server.start();
+}
+
+void wlcs::InProcessServer::TearDown()
+{
+    server.stop();
+}
+
+wlcs::Server& wlcs::InProcessServer::the_server()
+{
+    return server;
+}
+
+void throw_wayland_error(wl_display* display)
+{
+    auto err = wl_display_get_error(display);
+    if (err != EPROTO)
+    {
+        BOOST_THROW_EXCEPTION((std::system_error{
+            err,
+            std::system_category(),
+            "Error while dispatching Wayland events"
+        }));
+    }
+    else
+    {
+        uint32_t object_id;
+        uint32_t protocol_error;
+        wl_interface const* interface;
+        protocol_error = wl_display_get_protocol_error(display, &interface, &object_id);
+        BOOST_THROW_EXCEPTION((wlcs::ProtocolError{interface, protocol_error}));
+    }
+}
+
+class wlcs::Client::Impl
+{
+public:
+    Impl(Server& server)
+        : supported_extensions{server.supported_extensions()}
+    {
+        try
+        {
+            display = wl_display_connect_to_fd(server.create_client_socket());
+        }
+        catch (ShimNotImplemented const&)
+        {
+            // TODO: Warn about connecting to who-knows-what
+            display = wl_display_connect(NULL);
+        }
+
+        if (!display)
+        {
+            BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to connect to Wayland socket"}));
+        }
+
+        registry = wl_display_get_registry(display);
+        wl_registry_add_listener(registry, &registry_listener, this);
+
+        server_roundtrip();
+    }
+
+    ~Impl()
+    {
+        if (shm) wl_shm_destroy(shm);
+        if (shell) wl_shell_destroy(shell);
+        if (compositor) wl_compositor_destroy(compositor);
+        if (subcompositor) wl_subcompositor_destroy(subcompositor);
+        if (registry) wl_registry_destroy(registry);
+        if (seat) wl_seat_destroy(seat);
+        if (pointer) wl_pointer_destroy(pointer);
+        if (touch) wl_touch_destroy(touch);
+        if (xdg_shell_v6) zxdg_shell_v6_destroy(xdg_shell_v6);
+        if (xdg_shell_stable) xdg_wm_base_destroy(xdg_shell_stable);
+        for (auto const& output: outputs)
+            wl_output_destroy(output->current.output);
+        for (auto const& callback: destruction_callbacks)
+            callback();
+        destruction_callbacks.clear();
+        wl_display_disconnect(display);
+    }
+
+    struct wl_display* wl_display() const
+    {
+        return display;
+    }
+
+    struct wl_compositor* wl_compositor() const
+    {
+        return compositor;
+    }
+
+    struct wl_subcompositor* wl_subcompositor() const
+    {
+        return subcompositor;
+    }
+
+    struct wl_shm* wl_shm() const
+    {
+        return shm;
+    }
+
+    struct wl_seat* wl_seat() const
+    {
+        return seat;
+    }
+
+    void run_on_destruction(std::function<void()> callback)
+    {
+        destruction_callbacks.push_back(callback);
+    }
+
+    ShmBuffer const& create_buffer(Client& client, int width, int height)
+    {
+        auto buffer = std::make_shared<ShmBuffer>(client, width, height);
+        run_on_destruction([buffer]() mutable
+            {
+                buffer.reset();
+            });
+        return *buffer;
+    }
+
+    Surface create_wl_shell_surface(Client& client, int width, int height)
+    {
+        Surface surface{client};
+
+        wl_shell_surface * shell_surface = wl_shell_get_shell_surface(shell, surface);
+        surface.run_on_destruction([shell_surface]()
+            {
+                wl_shell_surface_destroy(shell_surface);
+            });
+        wl_shell_surface_set_toplevel(shell_surface);
+
+        wl_surface_commit(surface);
+
+        surface.attach_visible_buffer(width, height);
+
+        return surface;
+    }
+
+    Surface create_xdg_shell_v6_surface(Client& client, int width, int height)
+    {
+        Surface surface{client};
+
+        auto xdg_surface = std::make_shared<XdgSurfaceV6>(client, surface);
+        auto xdg_toplevel = std::make_shared<XdgToplevelV6>(*xdg_surface);
+
+        surface.run_on_destruction([xdg_surface, xdg_toplevel]() mutable
+            {
+                xdg_surface.reset();
+                xdg_toplevel.reset();
+            });
+
+        wl_surface_commit(surface);
+
+        surface.attach_visible_buffer(width, height);
+
+        return surface;
+    }
+
+    Surface create_xdg_shell_stable_surface(Client& client, int width, int height)
+    {
+        Surface surface{client};
+
+        auto xdg_surface = std::make_shared<XdgSurfaceStable>(client, surface);
+        auto xdg_toplevel = std::make_shared<XdgToplevelStable>(*xdg_surface);
+
+        surface.run_on_destruction([xdg_surface, xdg_toplevel]() mutable
+            {
+                xdg_surface.reset();
+                xdg_toplevel.reset();
+            });
+
+        wl_surface_commit(surface);
+
+        surface.attach_visible_buffer(width, height);
+
+        return surface;
+    }
+
+    wl_shell* the_shell() const
+    {
+        return shell;
+    }
+
+    zxdg_shell_v6* the_xdg_shell_v6() const
+    {
+        return xdg_shell_v6;
+    }
+
+    xdg_wm_base* the_xdg_shell_stable() const
+    {
+        return xdg_shell_stable;
+    }
+
+    wl_pointer* the_pointer() const { return pointer; }
+
+    wl_surface* window_under_cursor() const
+    {
+        if (pointer_events_pending())
+            BOOST_THROW_EXCEPTION(std::runtime_error("Pointer events pending"));
+        if (current_pointer_location)
+        {
+            return current_pointer_location->surface;
+        }
+        return nullptr;
+    }
+
+    wl_surface* touched_window() const
+    {
+        if (touch_events_pending())
+            BOOST_THROW_EXCEPTION(std::runtime_error("Touch events pending"));
+        wl_surface* surface = nullptr;
+        for (auto const& touch : current_touches)
+        {
+            if (surface && touch.second.surface != surface)
+                BOOST_THROW_EXCEPTION(std::runtime_error("Multiple surfaces have active touches"));
+            else
+                surface = touch.second.surface;
+        }
+        return surface;
+    }
+
+    std::pair<wl_fixed_t, wl_fixed_t> pointer_position() const
+    {
+        if (pointer_events_pending())
+            BOOST_THROW_EXCEPTION(std::runtime_error("Pointer events pending"));
+        return current_pointer_location.value().coordinates;
+    };
+
+    std::pair<wl_fixed_t, wl_fixed_t> touch_position() const
+    {
+        if (touch_events_pending())
+            BOOST_THROW_EXCEPTION(std::runtime_error("Touch events pending"));
+        if (current_touches.empty())
+            BOOST_THROW_EXCEPTION(std::runtime_error("No touches"));
+        else if (current_touches.size() == 1)
+            return current_touches.begin()->second.coordinates;
+        else
+            BOOST_THROW_EXCEPTION(std::runtime_error("More than one touches"));
+    };
+
+    bool pointer_events_pending() const
+    {
+        return !pending_buttons.empty() || pending_pointer_location;
+    }
+
+    bool touch_events_pending() const
+    {
+        return !pending_touches.empty() || !pending_up_touches.empty();
+    }
+
+    void add_pointer_enter_notification(PointerEnterNotifier const& on_enter)
+    {
+        enter_notifiers.push_back(on_enter);
+    }
+    void add_pointer_leave_notification(PointerLeaveNotifier const& on_leave)
+    {
+        leave_notifiers.push_back(on_leave);
+    }
+    void add_pointer_motion_notification(PointerMotionNotifier const& on_motion)
+    {
+        motion_notifiers.push_back(on_motion);
+    }
+    void add_pointer_button_notification(PointerButtonNotifier const& on_button)
+    {
+        button_notifiers.push_back(on_button);
+    }
+
+    void* bind_if_supported(wl_interface const& to_bind, VersionSpecifier const& version) const
+    {
+        std::experimental::optional<bool> expected_to_be_supported{};
+
+        if (supported_extensions)
+        {
+            expected_to_be_supported =
+                supported_extensions->count(to_bind.name) &&
+                version.select_version(supported_extensions->at(to_bind.name));
+        }
+
+        /* TODO: Mark tests using globals which exist, but are listed as unsupported as
+         * may-fail.
+         * This should help incrementally implementing a protocol, while getting test
+         * feedback.
+         */
+        auto const global = globals.find(to_bind.name);
+        if (global != globals.end())
+        {
+            auto const version_to_bind = version.select_version(global->second.version);
+            if (version_to_bind)
+            {
+                auto global_proxy = wl_registry_bind(registry, global->second.id, &to_bind, version_to_bind.value());
+                if (!global_proxy)
+                {
+                    throw_wayland_error(display);
+                }
+                return global_proxy;
+            }
+            else if (!expected_to_be_supported.value_or(true))
+            {
+                // We didn't expect to find the needed version of this extension anyway…
+                BOOST_THROW_EXCEPTION((ExtensionExpectedlyNotSupported{to_bind.name, version}));
+            }
+
+            BOOST_THROW_EXCEPTION((std::runtime_error{
+                "Failed to bind to " + std::string{to_bind.name} + " version " + version.describe()}));
+        }
+        else if (!expected_to_be_supported.value_or(true))
+        {
+            // We didn't expect to find this extension anyway…
+            BOOST_THROW_EXCEPTION((ExtensionExpectedlyNotSupported{to_bind.name, version}));
+        }
+        BOOST_THROW_EXCEPTION((std::runtime_error{
+            "Failed to bind to " + std::string{to_bind.name} + " version " + version.describe()}));
+    }
+
+    void dispatch_until(
+        std::function<bool()> const& predicate, std::chrono::seconds timeout)
+    {
+        using namespace std::chrono;
+
+        auto const end_time = steady_clock::now() + timeout;
+        while (!predicate())
+        {
+            while (wl_display_prepare_read(display) != 0)
+            {
+                if (wl_display_dispatch_pending(display) < 0)
+                    throw_wayland_error(display);
+            }
+            wl_display_flush(display);
+
+            auto const time_left = end_time - steady_clock::now();
+            if (time_left.count() < 0)
+            {
+                BOOST_THROW_EXCEPTION((Timeout{"Timeout waiting for condition"}));
+            }
+
+            /*
+             * TODO: We really want std::chrono::duration::ceil() here, but that's C++17
+             */
+            /*
+             * We want to wait *at least* as long as time_left. duration_cast<milliseconds>
+             * will perform integer division, so any fractional milliseconds will get dropped.
+             *
+             * Adding 1ms will ensure we wait until *after* we're meant to timeout.
+             */
+            auto const maximum_wait_ms = duration_cast<milliseconds>(time_left) + 1ms;
+            pollfd fd{
+                wl_display_get_fd(display),
+                POLLIN | POLLERR,
+                0
+            };
+
+            auto const poll_result = poll(&fd, 1, maximum_wait_ms.count());
+            if (poll_result < 0)
+            {
+                wl_display_cancel_read(display);
+                BOOST_THROW_EXCEPTION((std::system_error{
+                    errno,
+                    std::system_category(),
+                    "Failed to wait for Wayland event"}));
+            }
+
+            if (poll_result == 0)
+            {
+                wl_display_cancel_read(display);
+                BOOST_THROW_EXCEPTION((Timeout{"Timeout waiting for condition"}));
+            }
+
+            if (wl_display_read_events(display) < 0)
+            {
+                throw_wayland_error(display);
+            }
+
+            if (wl_display_dispatch_pending(display) < 0)
+            {
+                throw_wayland_error(display);
+            }
+        }
+    }
+
+    void server_roundtrip()
+    {
+        if (wl_display_roundtrip(display) < 0)
+        {
+            throw_wayland_error(display);
+        }
+    }
+
+    struct Output
+    {
+        OutputState current;
+        OutputState pending;
+        std::vector<std::function<void()>> done_notifiers;
+
+        Output(struct wl_output* output)
+            : current{output},
+              pending{output}
+        {
+        }
+
+        static void geometry_thunk(
+            void* ctx,
+            struct wl_output */*wl_output*/,
+            int32_t x,
+            int32_t y,
+            int32_t /*physical_width*/,
+            int32_t /*physical_height*/,
+            int32_t /*subpixel*/,
+            const char */*make*/,
+            const char */*model*/,
+            int32_t /*transform*/)
+        {
+            auto me = static_cast<Output*>(ctx);
+            me->pending.geometry_position = std::make_pair(x, y);
+        }
+
+        static void mode_thunk(
+            void* ctx,
+            struct wl_output */*wl_output*/,
+            uint32_t /*flags*/,
+            int32_t width,
+            int32_t height,
+            int32_t /*refresh*/)
+        {
+            auto me = static_cast<Output*>(ctx);
+            me->pending.mode_size = std::make_pair(width, height);
+        }
+
+        static void done_thunk(void* ctx, struct wl_output */*wl_output*/)
+        {
+            auto me = static_cast<Output*>(ctx);
+
+            if (me->pending.geometry_position)
+                me->current.geometry_position = me->pending.geometry_position;
+            if (me->pending.mode_size)
+                me->current.mode_size = me->pending.mode_size;
+            if (me->pending.scale)
+                me->current.scale = me->pending.scale;
+
+            me->pending = OutputState{me->current.output};
+
+            for (auto const& notifier : me->done_notifiers)
+                notifier();
+        }
+
+        static void scale_thunk(void* ctx, struct wl_output */*wl_output*/, int32_t factor)
+        {
+            auto me = static_cast<Output*>(ctx);
+            me->pending.scale = factor;
+        }
+
+        static constexpr wl_output_listener listener = {
+            &Impl::Output::geometry_thunk,
+            &Impl::Output::mode_thunk,
+            &Impl::Output::done_thunk,
+            &Impl::Output::scale_thunk,
+        };
+    };
+
+    std::vector<std::unique_ptr<Output>> outputs;
+
+private:
+    static void pointer_enter(
+        void* ctx,
+        wl_pointer* /*pointer*/,
+        uint32_t /*serial*/,
+        wl_surface* surface,
+        wl_fixed_t x,
+        wl_fixed_t y)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        if (me->current_pointer_location && !me->pending_pointer_leave)
+            FAIL()
+                << "Pointer tried to enter surface " << surface
+                << " without first leaving surface " << me->current_pointer_location.value().surface;
+
+        me->pending_pointer_location = SurfaceLocation{
+            surface,
+            std::make_pair(x, y)
+        };
+    }
+
+    static void pointer_leave(
+        void* ctx,
+        wl_pointer* /*pointer*/,
+        uint32_t /*serial*/,
+        wl_surface* surface)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        if (!me->current_pointer_location)
+            FAIL() << "Got wl_pointer.leave when the pointer was not on a surface";
+
+        // the surface should never be null along the wire, but may come out as null if it's been destroyed
+        if (surface != nullptr && surface != me->current_pointer_location.value().surface)
+            FAIL()
+                << "Got wl_pointer.leave with surface " << surface
+                << " instead of " << me->current_pointer_location.value().surface;
+
+        me->pending_pointer_location = std::experimental::nullopt;
+        me->pending_pointer_leave = true;
+    }
+
+    static void pointer_motion(
+        void* ctx,
+        wl_pointer* /*pointer*/,
+        uint32_t /*time*/,
+        wl_fixed_t x,
+        wl_fixed_t y)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        if (!me->current_pointer_location && !me->pending_pointer_location)
+            FAIL() << "Got wl_pointer.motion when the pointer was not on a surface";
+
+        if (!me->pending_pointer_location)
+            me->pending_pointer_location = me->current_pointer_location;
+        me->pending_pointer_location.value().coordinates = std::make_pair(x, y);
+    }
+
+    static void pointer_button(
+        void *ctx,
+        wl_pointer* /*wl_pointer*/,
+        uint32_t serial,
+        uint32_t /*time*/,
+        uint32_t button,
+        uint32_t state)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        me->pending_buttons[button] = std::make_pair(serial, state == WL_POINTER_BUTTON_STATE_PRESSED);
+    }
+
+    static void pointer_frame(void* ctx, wl_pointer* /*pointer*/)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        if (me->pending_pointer_leave)
+        {
+            if (!me->current_pointer_location)
+                FAIL() << "Pointer tried to leave when it was not on a surface";
+
+            wl_surface* old_surface = me->current_pointer_location.value().surface;
+            me->current_pointer_location = std::experimental::nullopt;
+            me->pending_pointer_leave = false;
+
+            me->notify_of_pointer_leave(old_surface);
+        }
+
+        if (me->pending_pointer_location)
+        {
+            auto const old_pointer_location = me->current_pointer_location;
+            me->current_pointer_location = me->pending_pointer_location;
+            me->pending_pointer_location = std::experimental::nullopt;
+
+            if (!old_pointer_location)
+            {
+                me->notify_of_pointer_enter(
+                    me->current_pointer_location.value().surface,
+                    me->current_pointer_location.value().coordinates);
+            }
+            else
+            {
+                me->notify_of_pointer_motion(me->current_pointer_location.value().coordinates);
+            }
+        }
+
+        if (!me->pending_buttons.empty())
+        {
+            me->notify_of_pointer_buttons(me->pending_buttons);
+            me->pending_buttons.clear();
+        }
+    }
+
+    void notify_of_pointer_enter(wl_surface* surface, std::pair<int, int> position)
+    {
+        std::vector<decltype(enter_notifiers)::const_iterator> to_remove;
+        for (auto notifier = enter_notifiers.begin(); notifier != enter_notifiers.end(); ++notifier)
+        {
+            if (!(*notifier)(surface, position.first, position.second))
+            {
+                to_remove.push_back(notifier);
+            }
+        }
+        for (auto removed : to_remove)
+        {
+            enter_notifiers.erase(removed);
+        }
+    }
+
+    void notify_of_pointer_leave(wl_surface* surface)
+    {
+        std::vector<decltype(leave_notifiers)::const_iterator> to_remove;
+        for (auto notifier = leave_notifiers.begin(); notifier != leave_notifiers.end(); ++notifier)
+        {
+            if (!(*notifier)(surface))
+            {
+                to_remove.push_back(notifier);
+            }
+        }
+        for (auto removed : to_remove)
+        {
+            leave_notifiers.erase(removed);
+        }
+    }
+
+    void notify_of_pointer_motion(std::pair<int, int> position)
+    {
+
+        std::vector<decltype(motion_notifiers)::const_iterator> to_remove;
+        for (auto notifier = motion_notifiers.begin(); notifier != motion_notifiers.end(); ++notifier)
+        {
+            if (!(*notifier)(position.first, position.second))
+            {
+                to_remove.push_back(notifier);
+            }
+        }
+        for (auto removed : to_remove)
+        {
+            motion_notifiers.erase(removed);
+        }
+    }
+
+    void notify_of_pointer_buttons(std::map<uint32_t, std::pair<uint32_t, bool>> const& buttons)
+    {
+        for (auto const& button : buttons)
+        {
+            std::vector<decltype(button_notifiers)::const_iterator> to_remove;
+            for (auto notifier = button_notifiers.begin(); notifier != button_notifiers.end(); ++notifier)
+            {
+                if (!(*notifier)(button.second.first, button.first, button.second.second))
+                {
+                    to_remove.push_back(notifier);
+                }
+            }
+            for (auto removed : to_remove)
+            {
+                button_notifiers.erase(removed);
+            }
+        }
+    }
+
+    static constexpr wl_pointer_listener pointer_listener = {
+        &Impl::pointer_enter,
+        &Impl::pointer_leave,
+        &Impl::pointer_motion,
+        &Impl::pointer_button,
+        nullptr,    // axis
+        &Impl::pointer_frame,    // frame
+        nullptr,    // axis_source
+        nullptr,    // axis_stop
+        nullptr     // axis_discrete
+    };
+
+    static void touch_down(
+        void* ctx,
+        wl_touch* /*wl_touch*/,
+        uint32_t /*serial*/,
+        uint32_t /*time*/,
+        wl_surface* surface,
+        int32_t id,
+        wl_fixed_t x,
+        wl_fixed_t y)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        auto touch = me->current_touches.find(id);
+        if (touch != me->current_touches.end())
+            FAIL() << "Got wl_touch.down with ID " << id << " which is already down";
+
+        me->pending_touches[id] = SurfaceLocation {
+            surface,
+            std::make_pair(x,y)
+        };
+    }
+
+    static void touch_up(
+        void* ctx,
+        wl_touch* /*wl_touch*/,
+        uint32_t /*serial*/,
+        uint32_t /*time*/,
+        int32_t id)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        auto touch = me->current_touches.find(id);
+        if (touch == me->current_touches.end())
+            FAIL() << "Got wl_touch.up with unknown ID " << id;
+
+        me->pending_up_touches.insert(id);
+    }
+
+       static void touch_motion(
+        void* ctx,
+        wl_touch* /*wl_touch*/,
+        uint32_t /*time*/,
+        int32_t id,
+        wl_fixed_t x,
+        wl_fixed_t y)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        auto touch = me->current_touches.find(id);
+        if (touch == me->current_touches.end())
+            FAIL() << "Got wl_touch.up with unknown ID " << id;
+
+        me->pending_touches[id] = SurfaceLocation {
+            touch->second.surface,
+            std::make_pair(x,y)
+        };
+    }
+
+    static void touch_frame(void* ctx, wl_touch* /*touch*/)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        for (auto const& id : me->pending_up_touches)
+        {
+            me->current_touches.erase(id);
+        }
+
+        for (auto const& touch : me->pending_touches)
+        {
+            me->current_touches[touch.first] = touch.second;
+        }
+
+        me->pending_up_touches.clear();
+        me->pending_touches.clear();
+    }
+
+    static constexpr wl_touch_listener touch_listener = {
+        &Impl::touch_down,
+        &Impl::touch_up,
+        &Impl::touch_motion,
+        &Impl::touch_frame,
+        nullptr,    // cancel
+        nullptr,    // shape
+        nullptr,    // orientation
+    };
+
+    static void seat_capabilities(
+        void* ctx,
+        struct wl_seat* seat,
+        uint32_t capabilities)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        if (capabilities & WL_SEAT_CAPABILITY_POINTER)
+        {
+            me->pointer = wl_seat_get_pointer(seat);
+            wl_pointer_add_listener(me->pointer, &pointer_listener, me);
+        }
+
+        if (capabilities & WL_SEAT_CAPABILITY_TOUCH)
+        {
+            me->touch = wl_seat_get_touch(seat);
+            wl_touch_add_listener(me->touch, &touch_listener, me);
+        }
+    }
+
+    static void seat_name(
+        void*,
+        struct wl_seat*,
+        char const*)
+    {
+    }
+
+    static constexpr wl_seat_listener seat_listener = {
+        &Impl::seat_capabilities,
+        &Impl::seat_name
+    };
+
+    static void global_handler(
+        void* ctx,
+        wl_registry* registry,
+        uint32_t id,
+        char const* interface,
+        uint32_t version)
+    {
+        using namespace std::literals::string_literals;
+
+        auto me = static_cast<Impl*>(ctx);
+        me->global_type_names[id] = interface;
+        me->globals[interface] = Global{id, version};
+
+        if ("wl_shm"s == interface)
+        {
+            me->shm = static_cast<struct wl_shm*>(
+                wl_registry_bind(registry, id, &wl_shm_interface, version));
+        }
+        else if ("wl_compositor"s == interface)
+        {
+            me->compositor = static_cast<struct wl_compositor*>(
+                wl_registry_bind(registry, id, &wl_compositor_interface, version));
+        }
+        else if ("wl_subcompositor"s == interface)
+        {
+            me->subcompositor = static_cast<struct wl_subcompositor*>(
+                wl_registry_bind(registry, id, &wl_subcompositor_interface, version));
+        }
+        else if ("wl_shell"s == interface)
+        {
+            me->shell = static_cast<struct wl_shell*>(
+                wl_registry_bind(registry, id, &wl_shell_interface, version));
+        }
+        else if ("wl_seat"s == interface)
+        {
+            me->seat = static_cast<struct wl_seat*>(
+                wl_registry_bind(registry, id, &wl_seat_interface, version));
+
+            wl_seat_add_listener(me->seat, &seat_listener, me);
+            // Ensure we receive the initial seat events.
+            me->server_roundtrip();
+        }
+        else if ("wl_output"s == interface)
+        {
+            auto wl_output = static_cast<struct wl_output*>(
+                wl_registry_bind(registry, id, &wl_output_interface, version));
+            auto output = std::make_unique<Output>(wl_output);
+            wl_output_add_listener(wl_output, &Output::listener, output.get());
+            me->outputs.push_back(move(output));
+
+            // Ensure we receive the initial output events.
+            me->server_roundtrip();
+        }
+        else if ("zxdg_shell_v6"s == interface)
+        {
+            me->xdg_shell_v6 = static_cast<struct zxdg_shell_v6*>(
+                wl_registry_bind(registry, id, &zxdg_shell_v6_interface, version));
+        }
+        else if ("xdg_wm_base"s == interface)
+        {
+            me->xdg_shell_stable = static_cast<struct xdg_wm_base*>(
+                wl_registry_bind(registry, id, &xdg_wm_base_interface, version));
+        }
+    }
+
+    static void global_removed(void* ctx, wl_registry*, uint32_t id)
+    {
+        auto me = static_cast<Impl*>(ctx);
+        auto const name = me->global_type_names.find(id);
+        if (name != me->global_type_names.end())
+        {
+            me->globals.erase(name->second);
+            me->global_type_names.erase(name);
+        }
+    }
+
+    constexpr static wl_registry_listener registry_listener = {
+        &global_handler,
+        &global_removed
+    };
+
+    std::shared_ptr<std::unordered_map<std::string, uint32_t> const> const supported_extensions;
+
+    struct wl_display* display;
+    struct wl_registry* registry = nullptr;
+    struct wl_compositor* compositor = nullptr;
+    struct wl_subcompositor* subcompositor = nullptr;
+    struct wl_shm* shm = nullptr;
+    struct wl_shell* shell = nullptr;
+    struct wl_seat* seat = nullptr;
+    struct wl_pointer* pointer = nullptr;
+    struct wl_touch* touch = nullptr;
+    struct zxdg_shell_v6* xdg_shell_v6 = nullptr;
+    std::vector<std::function<void()>> destruction_callbacks;
+    struct xdg_wm_base* xdg_shell_stable = nullptr;
+
+    struct Global
+    {
+        uint32_t id;
+        uint32_t version;
+    };
+    std::map<std::string, Global> globals;
+    std::map<uint32_t, std::string> global_type_names;
+
+    struct SurfaceLocation
+    {
+        wl_surface* surface;
+        std::pair<wl_fixed_t, wl_fixed_t> coordinates;
+    };
+    std::experimental::optional<SurfaceLocation> current_pointer_location;
+    std::experimental::optional<SurfaceLocation> pending_pointer_location;
+    bool pending_pointer_leave{false};
+    std::map<uint32_t, std::pair<uint32_t, bool>> pending_buttons; ///< Maps button id to the serial and if it's down
+    std::map<int, SurfaceLocation> current_touches; ///< Touches that have gotten a frame event
+    std::map<int, SurfaceLocation> pending_touches; ///< Touches that have gotten down or motion events without a frame
+    std::set<int> pending_up_touches; ///< Touches that have gotten up events without a frame
+
+    std::vector<PointerEnterNotifier> enter_notifiers;
+    std::vector<PointerLeaveNotifier> leave_notifiers;
+    std::vector<PointerMotionNotifier> motion_notifiers;
+    std::vector<PointerButtonNotifier> button_notifiers;
+};
+
+constexpr wl_pointer_listener wlcs::Client::Impl::pointer_listener;
+constexpr wl_touch_listener wlcs::Client::Impl::touch_listener;
+constexpr wl_seat_listener wlcs::Client::Impl::seat_listener;
+constexpr wl_output_listener wlcs::Client::Impl::Output::listener;
+constexpr wl_registry_listener wlcs::Client::Impl::registry_listener;
+
+wlcs::Client::Client(Server& server)
+    : impl{std::make_unique<Impl>(server)}
+{
+}
+
+wlcs::Client::~Client() = default;
+
+wlcs::Client::operator wl_display*() const
+{
+    return impl->wl_display();
+}
+
+wl_compositor* wlcs::Client::compositor() const
+{
+    return impl->wl_compositor();
+}
+
+wl_subcompositor* wlcs::Client::subcompositor() const
+{
+    return impl->wl_subcompositor();
+}
+
+wl_shm* wlcs::Client::shm() const
+{
+    return impl->wl_shm();
+}
+
+wl_seat* wlcs::Client::seat() const
+{
+    return impl->wl_seat();
+}
+
+void wlcs::Client::run_on_destruction(std::function<void()> callback)
+{
+    impl->run_on_destruction(callback);
+}
+
+wlcs::ShmBuffer const& wlcs::Client::create_buffer(int width, int height)
+{
+    return impl->create_buffer(*this, width, height);
+}
+
+wlcs::Surface wlcs::Client::create_wl_shell_surface(int width, int height)
+{
+    return impl->create_wl_shell_surface(*this, width, height);
+}
+
+wlcs::Surface wlcs::Client::create_xdg_shell_v6_surface(int width, int height)
+{
+    return impl->create_xdg_shell_v6_surface(*this, width, height);
+}
+
+wlcs::Surface wlcs::Client::create_xdg_shell_stable_surface(int width, int height)
+{
+    return impl->create_xdg_shell_stable_surface(*this, width, height);
+}
+
+wlcs::Surface wlcs::Client::create_visible_surface(int width, int height)
+{
+    return impl->create_wl_shell_surface(*this, width, height);
+}
+
+size_t wlcs::Client::output_count() const
+{
+    return impl->outputs.size();
+}
+
+wlcs::OutputState wlcs::Client::output_state(size_t index) const
+{
+    if (index > output_count())
+        throw std::out_of_range("Invalid output index");
+
+    return impl->outputs[index]->current;
+}
+
+void wlcs::Client::add_output_done_notifier(size_t index, std::function<void()> const& notifier)
+{
+    if (index > output_count())
+        throw std::runtime_error("Invalid output index");
+
+    impl->outputs[index]->done_notifiers.push_back(notifier);
+}
+
+wl_shell* wlcs::Client::shell() const
+{
+    return impl->the_shell();
+}
+
+zxdg_shell_v6* wlcs::Client::xdg_shell_v6() const
+{
+    return impl->the_xdg_shell_v6();
+}
+
+xdg_wm_base* wlcs::Client::xdg_shell_stable() const
+{
+    return impl->the_xdg_shell_stable();
+}
+
+wl_surface* wlcs::Client::window_under_cursor() const
+{
+    return impl->window_under_cursor();
+}
+
+wl_surface* wlcs::Client::touched_window() const
+{
+    return impl->touched_window();
+}
+
+std::pair<wl_fixed_t, wl_fixed_t> wlcs::Client::pointer_position() const
+{
+    return impl->pointer_position();
+}
+
+std::pair<wl_fixed_t, wl_fixed_t> wlcs::Client::touch_position() const
+{
+    return impl->touch_position();
+}
+
+void wlcs::Client::add_pointer_enter_notification(PointerEnterNotifier const& on_enter)
+{
+    impl->add_pointer_enter_notification(on_enter);
+}
+
+void wlcs::Client::add_pointer_leave_notification(PointerLeaveNotifier const& on_leave)
+{
+    impl->add_pointer_leave_notification(on_leave);
+}
+
+void wlcs::Client::add_pointer_motion_notification(PointerMotionNotifier const& on_motion)
+{
+    impl->add_pointer_motion_notification(on_motion);
+}
+
+void wlcs::Client::add_pointer_button_notification(PointerButtonNotifier const& on_button)
+{
+    impl->add_pointer_button_notification(on_button);
+}
+
+void wlcs::Client::dispatch_until(std::function<bool()> const& predicate, std::chrono::seconds timeout)
+{
+    impl->dispatch_until(predicate, timeout);
+}
+
+void wlcs::Client::roundtrip()
+{
+    impl->server_roundtrip();
+}
+
+void* wlcs::Client::bind_if_supported(wl_interface const& interface, VersionSpecifier const& version) const
+{
+    return impl->bind_if_supported(interface, version);
+}
+
+wl_pointer* wlcs::Client::the_pointer() const
+{
+    return impl->the_pointer();
+}
+
+class wlcs::Surface::Impl
+{
+public:
+    Impl(Client& client)
+        : surface_{wl_compositor_create_surface(client.compositor())},
+          owner_{client}
+    {
+    }
+
+    ~Impl()
+    {
+        for (auto i = 0u; i < pending_callbacks.size(); )
+        {
+            if (pending_callbacks[i].first == this)
+            {
+                auto pending_callback = pending_callbacks[i].second;
+                delete static_cast<std::function<void(uint32_t)>*>(wl_callback_get_user_data(pending_callback));
+
+                wl_callback_destroy(pending_callback);
+                pending_callbacks.erase(pending_callbacks.begin() + i);
+            }
+            else
+            {
+                ++i;
+            }
+        }
+
+        for (auto const& callback: destruction_callbacks)
+            callback();
+        destruction_callbacks.clear();
+
+        wl_surface_destroy(surface_);
+    }
+
+    wl_surface* surface() const
+    {
+        return surface_;
+    }
+
+    void attach_buffer(int width, int height)
+    {
+        auto& buffer = owner_.create_buffer(width, height);
+        wl_surface_attach(surface_, buffer, 0, 0);
+    }
+
+    void add_frame_callback(std::function<void(uint32_t)> const& on_frame)
+    {
+        std::unique_ptr<std::function<void(uint32_t)>> holder{
+            new std::function<void(uint32_t)>(on_frame)};
+
+        auto callback = wl_surface_frame(surface_);
+
+        pending_callbacks.push_back(std::make_pair(this, callback));
+
+        wl_callback_add_listener(callback, &frame_listener, holder.release());
+    }
+
+    void attach_visible_buffer(int width, int height)
+    {
+        attach_buffer(width, height);
+        auto surface_rendered = std::make_shared<bool>(false);
+        add_frame_callback([surface_rendered](auto) { *surface_rendered = true; });
+        wl_surface_commit(surface_);
+        owner_.dispatch_until([surface_rendered]() { return *surface_rendered; });
+    }
+
+    void run_on_destruction(std::function<void()> callback)
+    {
+        destruction_callbacks.push_back(callback);
+    }
+
+    Client& owner() const
+    {
+        return owner_;
+    }
+private:
+
+    static std::vector<std::pair<Impl const*, wl_callback*>> pending_callbacks;
+
+    static void frame_callback(void* ctx, wl_callback* callback, uint32_t frame_time)
+    {
+        auto us = std::find_if(
+            pending_callbacks.begin(),
+            pending_callbacks.end(),
+            [callback](auto const& elem)
+            {
+                return elem.second == callback;
+            });
+        pending_callbacks.erase(us);
+
+        auto frame_callback = static_cast<std::function<void(uint32_t)>*>(ctx);
+
+        (*frame_callback)(frame_time);
+
+        wl_callback_destroy(callback);
+        delete frame_callback;
+    }
+
+    static constexpr wl_callback_listener frame_listener = {
+        &frame_callback
+    };
+
+    struct wl_surface* const surface_;
+    Client& owner_;
+    std::vector<std::function<void()>> destruction_callbacks;
+};
+
+std::vector<std::pair<wlcs::Surface::Impl const*, wl_callback*>> wlcs::Surface::Impl::pending_callbacks;
+
+constexpr wl_callback_listener wlcs::Surface::Impl::frame_listener;
+
+wlcs::Surface::Surface(Client& client)
+    : impl{std::make_unique<Impl>(client)}
+{
+}
+
+wlcs::Surface::~Surface() = default;
+
+wlcs::Surface::Surface(Surface&&) = default;
+
+wlcs::Surface::operator wl_surface*() const
+{
+    return impl->surface();
+}
+
+void wlcs::Surface::attach_buffer(int width, int height)
+{
+    impl->attach_buffer(width, height);
+}
+
+void wlcs::Surface::add_frame_callback(std::function<void(int)> const& on_frame)
+{
+    impl->add_frame_callback(on_frame);
+}
+
+void wlcs::Surface::attach_visible_buffer(int width, int height)
+{
+    impl->attach_visible_buffer(width, height);
+}
+
+void wlcs::Surface::run_on_destruction(std::function<void()> callback)
+{
+    impl->run_on_destruction(callback);
+}
+
+wlcs::Client& wlcs::Surface::owner() const
+{
+    return impl->owner();
+}
+
+class wlcs::Subsurface::Impl
+{
+public:
+    Impl(Client& client, Surface& surface, Surface& parent)
+        : subsurface_{wl_subcompositor_get_subsurface(client.subcompositor(), surface, parent)},
+          parent_{parent}
+    {
+    }
+
+    struct wl_subsurface* const subsurface_;
+    Surface& parent_;
+};
+
+wlcs::Subsurface wlcs::Subsurface::create_visible(Surface& parent, int x, int y, int width, int height)
+{
+    wlcs::Subsurface subsurface{parent};
+    wl_subsurface_set_position(subsurface, x, y);
+
+    subsurface.attach_buffer(width, height);
+
+    bool surface_rendered{false};
+    subsurface.add_frame_callback([&surface_rendered](auto) { surface_rendered = true; });
+    wlcs::Surface* surface_ptr = &subsurface;
+    while (surface_ptr)
+    {
+        wl_surface_commit(*surface_ptr);
+        auto subsurface_ptr = dynamic_cast<wlcs::Subsurface*>(surface_ptr);
+        if (subsurface_ptr)
+            surface_ptr = &subsurface_ptr->parent();
+        else
+            surface_ptr = nullptr;
+    }
+    parent.owner().dispatch_until([&surface_rendered]() { return surface_rendered; });
+
+    return subsurface;
+}
+
+wlcs::Subsurface::Subsurface(wlcs::Surface& parent)
+    : Surface{parent.owner()},
+      impl{std::make_unique<wlcs::Subsurface::Impl>(parent.owner(), *this, parent)}
+{
+}
+
+wlcs::Subsurface::Subsurface(Subsurface &&) = default;
+
+wlcs::Subsurface::~Subsurface() = default;
+
+wlcs::Subsurface::operator wl_subsurface*() const
+{
+    return impl->subsurface_;
+}
+
+wlcs::Surface& wlcs::Subsurface::parent() const
+{
+    return impl->parent_;
+}
+
+class wlcs::ShmBuffer::Impl
+{
+public:
+    Impl(Client& client, int width, int height)
+    {
+        auto stride = width * 4;
+        auto size = stride * height;
+        auto fd = wlcs::helpers::create_anonymous_file(size);
+
+        auto pool = wl_shm_create_pool(client.shm(), fd, size);
+        buffer_ = wl_shm_pool_create_buffer(
+            pool,
+            0,
+            width,
+            height,
+            stride,
+            WL_SHM_FORMAT_ARGB8888);
+        wl_shm_pool_destroy(pool);
+        close(fd);
+
+        wl_buffer_add_listener(buffer_, &listener, this);
+    }
+
+    ~Impl()
+    {
+        wl_buffer_destroy(buffer_);
+    }
+
+    wl_buffer* buffer() const
+    {
+        return buffer_;
+    }
+
+    void add_release_listener(std::function<bool()> const& on_release)
+    {
+        release_notifiers.push_back(on_release);
+    }
+
+private:
+    static void on_release(void* ctx, wl_buffer* /*buffer*/)
+    {
+        auto me = static_cast<Impl*>(ctx);
+
+        std::vector<decltype(me->release_notifiers.begin())> expired_notifiers;
+
+        for (auto notifier = me->release_notifiers.begin(); notifier != me->release_notifiers.end(); ++notifier)
+        {
+            if (!(*notifier)())
+            {
+                expired_notifiers.push_back(notifier);
+            }
+        }
+        for (auto const& expired : expired_notifiers)
+            me->release_notifiers.erase(expired);
+    }
+
+    static constexpr wl_buffer_listener listener {
+        &on_release
+    };
+
+    wl_buffer* buffer_;
+    std::vector<std::function<bool()>> release_notifiers;
+};
+
+constexpr wl_buffer_listener wlcs::ShmBuffer::Impl::listener;
+
+wlcs::ShmBuffer::ShmBuffer(Client &client, int width, int height)
+    : impl{std::make_unique<Impl>(client, width, height)}
+{
+}
+
+wlcs::ShmBuffer::ShmBuffer(ShmBuffer&&) = default;
+wlcs::ShmBuffer::~ShmBuffer() = default;
+
+wlcs::ShmBuffer::operator wl_buffer*() const
+{
+    return impl->buffer();
+}
+
+void wlcs::ShmBuffer::add_release_listener(std::function<bool()> const &on_release)
+{
+    impl->add_release_listener(on_release);
+}
diff --git a/src/input_method.cpp b/src/input_method.cpp
new file mode 100644 (file)
index 0000000..19a8ef1
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "input_method.h"
+#include <gmock/gmock.h>
+
+auto wlcs::InputMethod::all_input_methods() -> std::vector<std::shared_ptr<InputMethod>>
+{
+    return {
+        std::make_shared<PointerInputMethod>(),
+        std::make_shared<TouchInputMethod>()};
+}
+
+struct wlcs::PointerInputMethod::Pointer : Device
+{
+    Pointer(wlcs::Server& server)
+        : pointer{server.create_pointer()}
+    {
+    }
+
+    void down_at(std::pair<int, int> position) override
+    {
+        ASSERT_THAT(button_down, testing::Eq(false)) << "Called down_at() with pointer already down";
+        pointer.move_to(position.first, position.second);
+        pointer.left_button_down();
+        button_down = true;
+    }
+
+    void move_to(std::pair<int, int> position) override
+    {
+        ASSERT_THAT(button_down, testing::Eq(true)) << "Called move_to() with pointer up";
+        pointer.move_to(position.first, position.second);
+    }
+
+    void up() override
+    {
+        ASSERT_THAT(button_down, testing::Eq(true)) << "Called up() with pointer already up";
+        pointer.left_button_up();
+        button_down = false;
+    }
+
+    wlcs::Pointer pointer;
+    bool button_down = false;
+};
+
+auto wlcs::PointerInputMethod::create_device(wlcs::Server& server) -> std::unique_ptr<Device>
+{
+    return std::make_unique<Pointer>(server);
+}
+
+auto wlcs::PointerInputMethod::current_surface(wlcs::Client const& client) -> wl_surface*
+{
+    return client.window_under_cursor();
+}
+
+auto wlcs::PointerInputMethod::position_on_surface(wlcs::Client const& client) -> std::pair<int, int>
+{
+    auto const wl_fixed_position = client.pointer_position();
+    return {
+        wl_fixed_to_int(wl_fixed_position.first),
+        wl_fixed_to_int(wl_fixed_position.second)};
+}
+
+struct wlcs::TouchInputMethod::Touch : Device
+{
+    Touch(wlcs::Server& server)
+        : touch{server.create_touch()}
+    {
+    }
+
+    void down_at(std::pair<int, int> position) override
+    {
+        ASSERT_THAT(is_down, testing::Eq(false)) << "Called down_at() with touch already down";
+        touch.down_at(position.first, position.second);
+        is_down = true;
+    }
+
+    void move_to(std::pair<int, int> position) override
+    {
+        ASSERT_THAT(is_down, testing::Eq(true)) << "Called move_to() with touch up";
+        touch.move_to(position.first, position.second);
+    }
+
+    void up() override
+    {
+        ASSERT_THAT(is_down, testing::Eq(true)) << "Called up() with touch already up";
+        touch.up();
+        is_down = false;
+    }
+
+    wlcs::Touch touch;
+    bool is_down = false;
+};
+
+auto wlcs::TouchInputMethod::create_device(wlcs::Server& server) -> std::unique_ptr<Device>
+{
+    return std::make_unique<Touch>(server);
+}
+
+auto wlcs::TouchInputMethod::current_surface(wlcs::Client const& client) -> wl_surface*
+{
+    return client.touched_window();
+}
+
+auto wlcs::TouchInputMethod::position_on_surface(wlcs::Client const& client) -> std::pair<int, int>
+{
+    auto const wl_fixed_position = client.touch_position();
+    return {
+        wl_fixed_to_int(wl_fixed_position.first),
+        wl_fixed_to_int(wl_fixed_position.second)};
+}
+
+std::ostream& std::operator<<(std::ostream& out, std::shared_ptr<wlcs::InputMethod> const& param)
+{
+    return out << param->name;
+}
diff --git a/src/layer_shell_v1.cpp b/src/layer_shell_v1.cpp
new file mode 100644 (file)
index 0000000..814a691
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright © 2018-2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "layer_shell_v1.h"
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+wlcs::LayerSurfaceV1::LayerSurfaceV1(
+    wlcs::Client& client,
+    wlcs::Surface& surface,
+    zwlr_layer_shell_v1_layer layer,
+    wl_output* output,
+    const char *_namespace)
+    : client{client},
+      layer_shell{client.bind_if_supported<zwlr_layer_shell_v1>(AnyVersion)},
+      layer_surface{wrap_wl_object(
+          zwlr_layer_shell_v1_get_layer_surface(
+          layer_shell,
+          surface,
+          output,
+          layer,
+          _namespace))}
+{
+    static struct zwlr_layer_surface_v1_listener const listener {
+        [](void* data,
+            struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1,
+            uint32_t serial,
+            uint32_t width,
+            uint32_t height)
+            {
+                auto self = static_cast<LayerSurfaceV1*>(data);
+                self->last_width_ = (int)width;
+                self->last_height_ = (int)height;
+                self->configure_count++;
+                (void)zwlr_layer_surface_v1;
+                (void)serial;
+                zwlr_layer_surface_v1_ack_configure(zwlr_layer_surface_v1, serial);
+            },
+        [](void* /*data*/,
+            struct zwlr_layer_surface_v1 */*zwlr_layer_surface_v1*/)
+            {
+            }
+    };
+    zwlr_layer_surface_v1_add_listener(layer_surface, &listener, this);
+}
+
+void wlcs::LayerSurfaceV1::dispatch_until_configure()
+{
+    client.dispatch_until([prev_config_count = configure_count, &current_config_count = configure_count]
+        {
+            return current_config_count > prev_config_count;
+        });
+}
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644 (file)
index 0000000..b77c79b
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include <gtest/gtest.h>
+#include <iostream>
+#include <memory>
+
+#include <dlfcn.h>
+
+#include "xfail_supporting_test_listener.h"
+#include "shared_library.h"
+#include "wlcs/display_server.h"
+
+#include "helpers.h"
+
+
+int main(int argc, char** argv)
+{
+    ::testing::InitGoogleTest(&argc, argv);
+
+    if (argc < 2 || !argv[1] || std::string{"--help"} == argv[1])
+    {
+        std::cerr
+            << "WayLand Conformance Suite test runner" << std::endl
+            << "Usage: " << argv[0] << " COMPOSITOR_INTEGRATION_MODULE [GTEST OPTIONS]... [COMPOSITOR_OPTIONS]..." << std::endl;
+        return 1;
+    }
+
+    auto const integration_filename = argv[1];
+
+    // Shuffle the integration module argument out of argv
+    for (auto i = 1 ; i < (argc - 1) ; ++i)
+    {
+        argv[i] = argv[i + 1];
+    }
+    wlcs::helpers::set_command_line(argc - 1, const_cast<char const**>(argv));
+
+    std::shared_ptr<wlcs::SharedLibrary> dso;
+    try
+    {
+        dso = std::make_shared<wlcs::SharedLibrary>(integration_filename);
+    }
+    catch (std::exception const& err)
+    {
+        std::cerr
+            << "Failed to load compositor integration module " << integration_filename << ": " << err.what() << std::endl;
+        return 1;
+    }
+
+    std::shared_ptr<WlcsServerIntegration const> entry_point;
+    try
+    {
+        entry_point = std::shared_ptr<WlcsServerIntegration const>{
+            dso,
+            dso->load_function<WlcsServerIntegration const*>("wlcs_server_integration")
+        };
+    }
+    catch (std::exception const& err)
+    {
+        std::cerr
+            << "Failed to load compositor entry point: " << err.what() << std::endl;
+        return 1;
+    }
+
+    wlcs::helpers::set_entry_point(entry_point);
+
+    auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
+    auto wrapping_listener = new testing::XFailSupportingTestListenerWrapper{
+        std::unique_ptr<::testing::TestEventListener>{
+            listeners.Release(listeners.default_result_printer())}};
+    listeners.Append(wrapping_listener);
+
+    /* (void)! is apparently the magical incantation required to get GCC to
+     * *actually* silently ignore the return value of a function declared with
+     * __attribute__(("warn_unused_result"))
+     * cf: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425
+     */
+    (void)!RUN_ALL_TESTS();
+    if (wrapping_listener->failed())
+    {
+        return EXIT_FAILURE;
+    }
+    return EXIT_SUCCESS;
+}
+
diff --git a/src/pointer_constraints_unstable_v1.cpp b/src/pointer_constraints_unstable_v1.cpp
new file mode 100644 (file)
index 0000000..623bc1c
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "pointer_constraints_unstable_v1.h"
+
+#include "in_process_server.h"
+#include <version_specifier.h>
+
+wlcs::ZwpPointerConstraintsV1::ZwpPointerConstraintsV1(Client& client) :
+    manager{client.bind_if_supported<zwp_pointer_constraints_v1>(AnyVersion)}
+{
+}
+
+wlcs::ZwpPointerConstraintsV1::~ZwpPointerConstraintsV1() = default;
+
+wlcs::ZwpPointerConstraintsV1::operator zwp_pointer_constraints_v1*() const
+{
+    return manager;
+}
+
+wlcs::ZwpConfinedPointerV1::ZwpConfinedPointerV1(
+    ZwpPointerConstraintsV1& manager,
+    wl_surface* surface,
+    wl_pointer* pointer,
+    wl_region* region,
+    uint32_t lifetime) :
+    relative_pointer{zwp_pointer_constraints_v1_confine_pointer(manager, surface, pointer, region, lifetime)},
+    version{zwp_confined_pointer_v1_get_version(relative_pointer)}
+{
+    zwp_confined_pointer_v1_set_user_data(relative_pointer, this);
+    zwp_confined_pointer_v1_add_listener(relative_pointer, &listener, this);
+}
+
+wlcs::ZwpConfinedPointerV1::~ZwpConfinedPointerV1()
+{
+    zwp_confined_pointer_v1_destroy(relative_pointer);
+}
+
+wlcs::ZwpConfinedPointerV1::operator zwp_confined_pointer_v1*() const
+{
+    return relative_pointer;
+}
+
+zwp_confined_pointer_v1_listener const wlcs::ZwpConfinedPointerV1::listener =
+    {
+        [](void* self, auto*) { static_cast<ZwpConfinedPointerV1*>(self)->confined(); },
+        [](void* self, auto*) { static_cast<ZwpConfinedPointerV1*>(self)->unconfined(); },
+    };
+
+
+wlcs::ZwpLockedPointerV1::ZwpLockedPointerV1(
+    ZwpPointerConstraintsV1& manager,
+    wl_surface* surface,
+    wl_pointer* pointer,
+    wl_region* region,
+    uint32_t lifetime) :
+    relative_pointer{zwp_pointer_constraints_v1_lock_pointer(manager, surface, pointer, region, lifetime)},
+    version{zwp_locked_pointer_v1_get_version(relative_pointer)}
+{
+    zwp_locked_pointer_v1_set_user_data(relative_pointer, this);
+    zwp_locked_pointer_v1_add_listener(relative_pointer, &listener, this);
+}
+
+wlcs::ZwpLockedPointerV1::~ZwpLockedPointerV1()
+{
+    zwp_locked_pointer_v1_destroy(relative_pointer);
+}
+
+wlcs::ZwpLockedPointerV1::operator zwp_locked_pointer_v1*() const
+{
+    return relative_pointer;
+}
+
+zwp_locked_pointer_v1_listener const wlcs::ZwpLockedPointerV1::listener =
+    {
+        [](void* self, auto*) { static_cast<ZwpLockedPointerV1*>(self)->locked(); },
+        [](void* self, auto*) { static_cast<ZwpLockedPointerV1*>(self)->unlocked(); },
+    };
diff --git a/src/primary_selection.cpp b/src/primary_selection.cpp
new file mode 100644 (file)
index 0000000..85bb0c1
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "primary_selection.h"
+
+#include <unistd.h>
+
+wlcs::ActiveListeners wlcs::PrimarySelectionDeviceListener::active_listeners;
+constexpr zwp_primary_selection_device_v1_listener wlcs::PrimarySelectionDeviceListener::thunks;
+
+wlcs::ActiveListeners wlcs::PrimarySelectionOfferListener::active_listeners;
+constexpr zwp_primary_selection_offer_v1_listener wlcs::PrimarySelectionOfferListener::thunks;
+
+wlcs::ActiveListeners wlcs::PrimarySelectionSourceListener::active_listeners;
+constexpr zwp_primary_selection_source_v1_listener wlcs::PrimarySelectionSourceListener::thunks;
+
+
+void wlcs::PrimarySelectionDeviceListener::data_offer(
+        void* data,
+        zwp_primary_selection_device_v1* device,
+        zwp_primary_selection_offer_v1* offer)
+{
+    if (active_listeners.includes(data))
+        static_cast<PrimarySelectionDeviceListener*>(data)->data_offer(device, offer);
+}
+
+void wlcs::PrimarySelectionDeviceListener::data_offer(zwp_primary_selection_device_v1*, zwp_primary_selection_offer_v1*)
+{
+}
+
+void wlcs::PrimarySelectionDeviceListener::selection(zwp_primary_selection_device_v1*, zwp_primary_selection_offer_v1*)
+{
+}
+
+void wlcs::PrimarySelectionDeviceListener::selection(
+        void* data,
+        zwp_primary_selection_device_v1* device,
+        zwp_primary_selection_offer_v1* offer)
+{
+    if (active_listeners.includes(data))
+        static_cast<PrimarySelectionDeviceListener*>(data)->selection(device, offer);
+}
+
+void wlcs::PrimarySelectionOfferListener::offer(zwp_primary_selection_offer_v1*, const char*)
+{
+}
+
+void wlcs::PrimarySelectionOfferListener::offer(
+        void* data, zwp_primary_selection_offer_v1* offer, const char* mime_type)
+{
+    if (active_listeners.includes(data))
+        static_cast<PrimarySelectionOfferListener*>(data)->offer(offer, mime_type);
+}
+
+void wlcs::PrimarySelectionSourceListener::send(zwp_primary_selection_source_v1*, const char*, int32_t fd)
+{
+    close(fd);
+}
+
+void wlcs::PrimarySelectionSourceListener::cancelled(zwp_primary_selection_source_v1*)
+{
+}
+
+void wlcs::PrimarySelectionSourceListener::send(
+        void* data, zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd)
+{
+    if (active_listeners.includes(data))
+        static_cast<PrimarySelectionSourceListener*>(data)->send(source, mime_type, fd);
+}
+
+void wlcs::PrimarySelectionSourceListener::cancelled(void* data, zwp_primary_selection_source_v1* source)
+{
+    if (active_listeners.includes(data))
+        static_cast<PrimarySelectionSourceListener*>(data)->cancelled(source);
+}
diff --git a/src/protocol/gtk-primary-selection.xml b/src/protocol/gtk-primary-selection.xml
new file mode 100644 (file)
index 0000000..aee85c2
--- /dev/null
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="gtk_primary_selection">
+    <copyright>
+        Copyright © 2015, 2016 Red Hat
+
+        Permission is hereby granted, free of charge, to any person obtaining a
+        copy of this software and associated documentation files (the "Software"),
+        to deal in the Software without restriction, including without limitation
+        the rights to use, copy, modify, merge, publish, distribute, sublicense,
+        and/or sell copies of the Software, and to permit persons to whom the
+        Software is furnished to do so, subject to the following conditions:
+
+        The above copyright notice and this permission notice (including the next
+        paragraph) shall be included in all copies or substantial portions of the
+        Software.
+
+        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+        THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+        FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+        DEALINGS IN THE SOFTWARE.
+    </copyright>
+
+    <description summary="Primary selection protocol">
+        This protocol provides the ability to have a primary selection device to
+        match that of the X server. This primary selection is a shortcut to the
+        common clipboard selection, where text just needs to be selected in order
+        to allow copying it elsewhere. The de facto way to perform this action
+        is the middle mouse button, although it is not limited to this one.
+
+        Clients wishing to honor primary selection should create a primary
+        selection source and set it as the selection through
+        wp_primary_selection_device.set_selection whenever the text selection
+        changes. In order to minimize calls in pointer-driven text selection,
+        it should happen only once after the operation finished. Similarly,
+        a NULL source should be set when text is unselected.
+
+        wp_primary_selection_offer objects are first announced through the
+        wp_primary_selection_device.data_offer event. Immediately after this event,
+        the primary data offer will emit wp_primary_selection_offer.offer events
+        to let know of the mime types being offered.
+
+        When the primary selection changes, the client with the keyboard focus
+        will receive wp_primary_selection_device.selection events. Only the client
+        with the keyboard focus will receive such events with a non-NULL
+        wp_primary_selection_offer. Across keyboard focus changes, previously
+        focused clients will receive wp_primary_selection_device.events with a
+        NULL wp_primary_selection_offer.
+
+        In order to request the primary selection data, the client must pass
+        a recent serial pertaining to the press event that is triggering the
+        operation, if the compositor deems the serial valid and recent, the
+        wp_primary_selection_source.send event will happen in the other end
+        to let the transfer begin. The client owning the primary selection
+        should write the requested data, and close the file descriptor
+        immediately.
+
+        If the primary selection owner client disappeared during the transfer,
+        the client reading the data will receive a
+        wp_primary_selection_device.selection event with a NULL
+        wp_primary_selection_offer, the client should take this as a hint
+        to finish the reads related to the no longer existing offer.
+
+        The primary selection owner should be checking for errors during
+        writes, merely cancelling the ongoing transfer if any happened.
+    </description>
+
+    <interface name="gtk_primary_selection_device_manager" version="1">
+        <description summary="X primary selection emulation">
+            The primary selection device manager is a singleton global object that
+            provides access to the primary selection. It allows to create
+            wp_primary_selection_source objects, as well as retrieving the per-seat
+            wp_primary_selection_device objects.
+        </description>
+
+        <request name="create_source">
+            <description summary="create a new primary selection source">
+                Create a new primary selection source.
+            </description>
+            <arg name="id" type="new_id" interface="gtk_primary_selection_source"/>
+        </request>
+
+        <request name="get_device">
+            <description summary="create a new primary selection device">
+                Create a new data device for a given seat.
+            </description>
+            <arg name="id" type="new_id" interface="gtk_primary_selection_device"/>
+            <arg name="seat" type="object" interface="wl_seat"/>
+        </request>
+
+        <request name="destroy" type="destructor">
+            <description summary="destroy the primary selection device manager">
+                Destroy the primary selection device manager.
+            </description>
+        </request>
+    </interface>
+
+    <interface name="gtk_primary_selection_device" version="1">
+        <request name="set_selection">
+            <description summary="set the primary selection">
+                Replaces the current selection. The previous owner of the primary selection
+                will receive a wp_primary_selection_source.cancelled event.
+
+                To unset the selection, set the source to NULL.
+            </description>
+            <arg name="source" type="object" interface="gtk_primary_selection_source" allow-null="true"/>
+            <arg name="serial" type="uint" summary="serial of the event that triggered this request"/>
+        </request>
+
+        <event name="data_offer">
+            <description summary="introduce a new wp_primary_selection_offer">
+                Introduces a new wp_primary_selection_offer object that may be used
+                to receive the current primary selection. Immediately following this
+                event, the new wp_primary_selection_offer object will send
+                wp_primary_selection_offer.offer events to describe the offered mime
+                types.
+            </description>
+            <arg name="offer" type="new_id" interface="gtk_primary_selection_offer"/>
+        </event>
+
+        <event name="selection">
+            <description summary="advertise a new primary selection">
+                The wp_primary_selection_device.selection event is sent to notify the
+                client of a new primary selection. This event is sent after the
+                wp_primary_selection.data_offer event introducing this object, and after
+                the offer has announced its mimetypes through
+                wp_primary_selection_offer.offer.
+
+                The data_offer is valid until a new offer or NULL is received
+                or until the client loses keyboard focus. The client must destroy the
+                previous selection data_offer, if any, upon receiving this event.
+            </description>
+            <arg name="id" type="object" interface="gtk_primary_selection_offer" allow-null="true"/>
+        </event>
+
+        <request name="destroy" type="destructor">
+            <description summary="destroy the primary selection device">
+                Destroy the primary selection device.
+            </description>
+        </request>
+    </interface>
+
+    <interface name="gtk_primary_selection_offer" version="1">
+        <description summary="offer to transfer primary selection contents">
+            A wp_primary_selection_offer represents an offer to transfer the contents
+            of the primary selection clipboard to the client. Similar to
+            wl_data_offer, the offer also describes the mime types that the source
+            will transferthat the
+            data can be converted to and provides the mechanisms for transferring the
+            data directly to the client.
+        </description>
+
+        <request name="receive">
+            <description summary="request that the data is transferred">
+                To transfer the contents of the primary selection clipboard, the client
+                issues this request and indicates the mime type that it wants to
+                receive. The transfer happens through the passed file descriptor
+                (typically created with the pipe system call). The source client writes
+                the data in the mime type representation requested and then closes the
+                file descriptor.
+
+                The receiving client reads from the read end of the pipe until EOF and
+                closes its end, at which point the transfer is complete.
+            </description>
+            <arg name="mime_type" type="string"/>
+            <arg name="fd" type="fd"/>
+        </request>
+
+        <request name="destroy" type="destructor">
+            <description summary="destroy the primary selection offer">
+                Destroy the primary selection offer.
+            </description>
+        </request>
+
+        <event name="offer">
+            <description summary="advertise offered mime type">
+                Sent immediately after creating announcing the wp_primary_selection_offer
+                through wp_primary_selection_device.data_offer. One event is sent per
+                offered mime type.
+            </description>
+            <arg name="mime_type" type="string"/>
+        </event>
+    </interface>
+
+    <interface name="gtk_primary_selection_source" version="1">
+        <description summary="offer to replace the contents of the primary selection">
+            The source side of a wp_primary_selection_offer, it provides a way to
+            describe the offered data and respond to requests to transfer the
+            requested contents of the primary selection clipboard.
+        </description>
+
+        <request name="offer">
+            <description summary="add an offered mime type">
+                This request adds a mime type to the set of mime types advertised to
+                targets. Can be called several times to offer multiple types.
+            </description>
+            <arg name="mime_type" type="string"/>
+        </request>
+
+        <request name="destroy" type="destructor">
+            <description summary="destroy the primary selection source">
+                Destroy the primary selection source.
+            </description>
+        </request>
+
+        <event name="send">
+            <description summary="send the primary selection contents">
+                Request for the current primary selection contents from the client.
+                Send the specified mime type over the passed file descriptor, then
+                close it.
+            </description>
+            <arg name="mime_type" type="string"/>
+            <arg name="fd" type="fd"/>
+        </event>
+
+        <event name="cancelled">
+            <description summary="request for primary selection contents was canceled">
+                This primary selection source is no longer valid. The client should
+                clean up and destroy this primary selection source.
+            </description>
+        </event>
+    </interface>
+</protocol>
\ No newline at end of file
diff --git a/src/protocol/pointer-constraints-unstable-v1.xml b/src/protocol/pointer-constraints-unstable-v1.xml
new file mode 100644 (file)
index 0000000..4e67a13
--- /dev/null
@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="pointer_constraints_unstable_v1">
+
+  <copyright>
+    Copyright © 2014      Jonas Ådahl
+    Copyright © 2015      Red Hat Inc.
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="protocol for constraining pointer motions">
+    This protocol specifies a set of interfaces used for adding constraints to
+    the motion of a pointer. Possible constraints include confining pointer
+    motions to a given region, or locking it to its current position.
+
+    In order to constrain the pointer, a client must first bind the global
+    interface "wp_pointer_constraints" which, if a compositor supports pointer
+    constraints, is exposed by the registry. Using the bound global object, the
+    client uses the request that corresponds to the type of constraint it wants
+    to make. See wp_pointer_constraints for more details.
+
+    Warning! The protocol described in this file is experimental and backward
+    incompatible changes may be made. Backward compatible changes may be added
+    together with the corresponding interface version bump. Backward
+    incompatible changes are done by bumping the version number in the protocol
+    and interface names and resetting the interface version. Once the protocol
+    is to be declared stable, the 'z' prefix and the version number in the
+    protocol and interface names are removed and the interface version number is
+    reset.
+  </description>
+
+  <interface name="zwp_pointer_constraints_v1" version="1">
+    <description summary="constrain the movement of a pointer">
+      The global interface exposing pointer constraining functionality. It
+      exposes two requests: lock_pointer for locking the pointer to its
+      position, and confine_pointer for locking the pointer to a region.
+
+      The lock_pointer and confine_pointer requests create the objects
+      wp_locked_pointer and wp_confined_pointer respectively, and the client can
+      use these objects to interact with the lock.
+
+      For any surface, only one lock or confinement may be active across all
+      wl_pointer objects of the same seat. If a lock or confinement is requested
+      when another lock or confinement is active or requested on the same surface
+      and with any of the wl_pointer objects of the same seat, an
+      'already_constrained' error will be raised.
+    </description>
+
+    <enum name="error">
+      <description summary="wp_pointer_constraints error values">
+       These errors can be emitted in response to wp_pointer_constraints
+       requests.
+      </description>
+      <entry name="already_constrained" value="1"
+            summary="pointer constraint already requested on that surface"/>
+    </enum>
+
+    <enum name="lifetime">
+      <description summary="constraint lifetime">
+       These values represent different lifetime semantics. They are passed
+       as arguments to the factory requests to specify how the constraint
+       lifetimes should be managed.
+      </description>
+      <entry name="oneshot" value="1">
+       <description summary="the pointer constraint is defunct once deactivated">
+         A oneshot pointer constraint will never reactivate once it has been
+         deactivated. See the corresponding deactivation event
+         (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for
+         details.
+       </description>
+      </entry>
+      <entry name="persistent" value="2">
+       <description summary="the pointer constraint may reactivate">
+         A persistent pointer constraint may again reactivate once it has
+         been deactivated. See the corresponding deactivation event
+         (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for
+         details.
+       </description>
+      </entry>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the pointer constraints manager object">
+       Used by the client to notify the server that it will no longer use this
+       pointer constraints object.
+      </description>
+    </request>
+
+    <request name="lock_pointer">
+      <description summary="lock pointer to a position">
+       The lock_pointer request lets the client request to disable movements of
+       the virtual pointer (i.e. the cursor), effectively locking the pointer
+       to a position. This request may not take effect immediately; in the
+       future, when the compositor deems implementation-specific constraints
+       are satisfied, the pointer lock will be activated and the compositor
+       sends a locked event.
+
+       The protocol provides no guarantee that the constraints are ever
+       satisfied, and does not require the compositor to send an error if the
+       constraints cannot ever be satisfied. It is thus possible to request a
+       lock that will never activate.
+
+       There may not be another pointer constraint of any kind requested or
+       active on the surface for any of the wl_pointer objects of the seat of
+       the passed pointer when requesting a lock. If there is, an error will be
+       raised. See general pointer lock documentation for more details.
+
+       The intersection of the region passed with this request and the input
+       region of the surface is used to determine where the pointer must be
+       in order for the lock to activate. It is up to the compositor whether to
+       warp the pointer or require some kind of user interaction for the lock
+       to activate. If the region is null the surface input region is used.
+
+       A surface may receive pointer focus without the lock being activated.
+
+       The request creates a new object wp_locked_pointer which is used to
+       interact with the lock as well as receive updates about its state. See
+       the the description of wp_locked_pointer for further information.
+
+       Note that while a pointer is locked, the wl_pointer objects of the
+       corresponding seat will not emit any wl_pointer.motion events, but
+       relative motion events will still be emitted via wp_relative_pointer
+       objects of the same seat. wl_pointer.axis and wl_pointer.button events
+       are unaffected.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_locked_pointer_v1"/>
+      <arg name="surface" type="object" interface="wl_surface"
+          summary="surface to lock pointer to"/>
+      <arg name="pointer" type="object" interface="wl_pointer"
+          summary="the pointer that should be locked"/>
+      <arg name="region" type="object" interface="wl_region" allow-null="true"
+          summary="region of surface"/>
+      <arg name="lifetime" type="uint" summary="lock lifetime"/>
+    </request>
+
+    <request name="confine_pointer">
+      <description summary="confine pointer to a region">
+       The confine_pointer request lets the client request to confine the
+       pointer cursor to a given region. This request may not take effect
+       immediately; in the future, when the compositor deems implementation-
+       specific constraints are satisfied, the pointer confinement will be
+       activated and the compositor sends a confined event.
+
+       The intersection of the region passed with this request and the input
+       region of the surface is used to determine where the pointer must be
+       in order for the confinement to activate. It is up to the compositor
+       whether to warp the pointer or require some kind of user interaction for
+       the confinement to activate. If the region is null the surface input
+       region is used.
+
+       The request will create a new object wp_confined_pointer which is used
+       to interact with the confinement as well as receive updates about its
+       state. See the the description of wp_confined_pointer for further
+       information.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_confined_pointer_v1"/>
+      <arg name="surface" type="object" interface="wl_surface"
+          summary="surface to lock pointer to"/>
+      <arg name="pointer" type="object" interface="wl_pointer"
+          summary="the pointer that should be confined"/>
+      <arg name="region" type="object" interface="wl_region" allow-null="true"
+          summary="region of surface"/>
+      <arg name="lifetime" type="uint" summary="confinement lifetime"/>
+    </request>
+  </interface>
+
+  <interface name="zwp_locked_pointer_v1" version="1">
+    <description summary="receive relative pointer motion events">
+      The wp_locked_pointer interface represents a locked pointer state.
+
+      While the lock of this object is active, the wl_pointer objects of the
+      associated seat will not emit any wl_pointer.motion events.
+
+      This object will send the event 'locked' when the lock is activated.
+      Whenever the lock is activated, it is guaranteed that the locked surface
+      will already have received pointer focus and that the pointer will be
+      within the region passed to the request creating this object.
+
+      To unlock the pointer, send the destroy request. This will also destroy
+      the wp_locked_pointer object.
+
+      If the compositor decides to unlock the pointer the unlocked event is
+      sent. See wp_locked_pointer.unlock for details.
+
+      When unlocking, the compositor may warp the cursor position to the set
+      cursor position hint. If it does, it will not result in any relative
+      motion events emitted via wp_relative_pointer.
+
+      If the surface the lock was requested on is destroyed and the lock is not
+      yet activated, the wp_locked_pointer object is now defunct and must be
+      destroyed.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the locked pointer object">
+       Destroy the locked pointer object. If applicable, the compositor will
+       unlock the pointer.
+      </description>
+    </request>
+
+    <request name="set_cursor_position_hint">
+      <description summary="set the pointer cursor position hint">
+       Set the cursor position hint relative to the top left corner of the
+       surface.
+
+       If the client is drawing its own cursor, it should update the position
+       hint to the position of its own cursor. A compositor may use this
+       information to warp the pointer upon unlock in order to avoid pointer
+       jumps.
+
+       The cursor position hint is double buffered. The new hint will only take
+       effect when the associated surface gets it pending state applied. See
+       wl_surface.commit for details.
+      </description>
+      <arg name="surface_x" type="fixed"
+          summary="surface-local x coordinate"/>
+      <arg name="surface_y" type="fixed"
+          summary="surface-local y coordinate"/>
+    </request>
+
+    <request name="set_region">
+      <description summary="set a new lock region">
+       Set a new region used to lock the pointer.
+
+       The new lock region is double-buffered. The new lock region will
+       only take effect when the associated surface gets its pending state
+       applied. See wl_surface.commit for details.
+
+       For details about the lock region, see wp_locked_pointer.
+      </description>
+      <arg name="region" type="object" interface="wl_region" allow-null="true"
+          summary="region of surface"/>
+    </request>
+
+    <event name="locked">
+      <description summary="lock activation event">
+       Notification that the pointer lock of the seat's pointer is activated.
+      </description>
+    </event>
+
+    <event name="unlocked">
+      <description summary="lock deactivation event">
+       Notification that the pointer lock of the seat's pointer is no longer
+       active. If this is a oneshot pointer lock (see
+       wp_pointer_constraints.lifetime) this object is now defunct and should
+       be destroyed. If this is a persistent pointer lock (see
+       wp_pointer_constraints.lifetime) this pointer lock may again
+       reactivate in the future.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="zwp_confined_pointer_v1" version="1">
+    <description summary="confined pointer object">
+      The wp_confined_pointer interface represents a confined pointer state.
+
+      This object will send the event 'confined' when the confinement is
+      activated. Whenever the confinement is activated, it is guaranteed that
+      the surface the pointer is confined to will already have received pointer
+      focus and that the pointer will be within the region passed to the request
+      creating this object. It is up to the compositor to decide whether this
+      requires some user interaction and if the pointer will warp to within the
+      passed region if outside.
+
+      To unconfine the pointer, send the destroy request. This will also destroy
+      the wp_confined_pointer object.
+
+      If the compositor decides to unconfine the pointer the unconfined event is
+      sent. The wp_confined_pointer object is at this point defunct and should
+      be destroyed.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the confined pointer object">
+       Destroy the confined pointer object. If applicable, the compositor will
+       unconfine the pointer.
+      </description>
+    </request>
+
+    <request name="set_region">
+      <description summary="set a new confine region">
+       Set a new region used to confine the pointer.
+
+       The new confine region is double-buffered. The new confine region will
+       only take effect when the associated surface gets its pending state
+       applied. See wl_surface.commit for details.
+
+       If the confinement is active when the new confinement region is applied
+       and the pointer ends up outside of newly applied region, the pointer may
+       warped to a position within the new confinement region. If warped, a
+       wl_pointer.motion event will be emitted, but no
+       wp_relative_pointer.relative_motion event.
+
+       The compositor may also, instead of using the new region, unconfine the
+       pointer.
+
+       For details about the confine region, see wp_confined_pointer.
+      </description>
+      <arg name="region" type="object" interface="wl_region" allow-null="true"
+          summary="region of surface"/>
+    </request>
+
+    <event name="confined">
+      <description summary="pointer confined">
+       Notification that the pointer confinement of the seat's pointer is
+       activated.
+      </description>
+    </event>
+
+    <event name="unconfined">
+      <description summary="pointer unconfined">
+       Notification that the pointer confinement of the seat's pointer is no
+       longer active. If this is a oneshot pointer confinement (see
+       wp_pointer_constraints.lifetime) this object is now defunct and should
+       be destroyed. If this is a persistent pointer confinement (see
+       wp_pointer_constraints.lifetime) this pointer confinement may again
+       reactivate in the future.
+      </description>
+    </event>
+  </interface>
+
+</protocol>
diff --git a/src/protocol/primary-selection-unstable-v1.xml b/src/protocol/primary-selection-unstable-v1.xml
new file mode 100644 (file)
index 0000000..e5a39e3
--- /dev/null
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wp_primary_selection_unstable_v1">
+  <copyright>
+    Copyright © 2015, 2016 Red Hat
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="Primary selection protocol">
+    This protocol provides the ability to have a primary selection device to
+    match that of the X server. This primary selection is a shortcut to the
+    common clipboard selection, where text just needs to be selected in order
+    to allow copying it elsewhere. The de facto way to perform this action
+    is the middle mouse button, although it is not limited to this one.
+
+    Clients wishing to honor primary selection should create a primary
+    selection source and set it as the selection through
+    wp_primary_selection_device.set_selection whenever the text selection
+    changes. In order to minimize calls in pointer-driven text selection,
+    it should happen only once after the operation finished. Similarly,
+    a NULL source should be set when text is unselected.
+
+    wp_primary_selection_offer objects are first announced through the
+    wp_primary_selection_device.data_offer event. Immediately after this event,
+    the primary data offer will emit wp_primary_selection_offer.offer events
+    to let know of the mime types being offered.
+
+    When the primary selection changes, the client with the keyboard focus
+    will receive wp_primary_selection_device.selection events. Only the client
+    with the keyboard focus will receive such events with a non-NULL
+    wp_primary_selection_offer. Across keyboard focus changes, previously
+    focused clients will receive wp_primary_selection_device.events with a
+    NULL wp_primary_selection_offer.
+
+    In order to request the primary selection data, the client must pass
+    a recent serial pertaining to the press event that is triggering the
+    operation, if the compositor deems the serial valid and recent, the
+    wp_primary_selection_source.send event will happen in the other end
+    to let the transfer begin. The client owning the primary selection
+    should write the requested data, and close the file descriptor
+    immediately.
+
+    If the primary selection owner client disappeared during the transfer,
+    the client reading the data will receive a
+    wp_primary_selection_device.selection event with a NULL
+    wp_primary_selection_offer, the client should take this as a hint
+    to finish the reads related to the no longer existing offer.
+
+    The primary selection owner should be checking for errors during
+    writes, merely cancelling the ongoing transfer if any happened.
+  </description>
+
+  <interface name="zwp_primary_selection_device_manager_v1" version="1">
+    <description summary="X primary selection emulation">
+      The primary selection device manager is a singleton global object that
+      provides access to the primary selection. It allows to create
+      wp_primary_selection_source objects, as well as retrieving the per-seat
+      wp_primary_selection_device objects.
+    </description>
+
+    <request name="create_source">
+      <description summary="create a new primary selection source">
+        Create a new primary selection source.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_primary_selection_source_v1"/>
+    </request>
+
+    <request name="get_device">
+      <description summary="create a new primary selection device">
+        Create a new data device for a given seat.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_primary_selection_device_v1"/>
+      <arg name="seat" type="object" interface="wl_seat"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection device manager">
+        Destroy the primary selection device manager.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="zwp_primary_selection_device_v1" version="1">
+    <request name="set_selection">
+      <description summary="set the primary selection">
+        Replaces the current selection. The previous owner of the primary
+        selection will receive a wp_primary_selection_source.cancelled event.
+
+        To unset the selection, set the source to NULL.
+      </description>
+      <arg name="source" type="object" interface="zwp_primary_selection_source_v1" allow-null="true"/>
+      <arg name="serial" type="uint" summary="serial of the event that triggered this request"/>
+    </request>
+
+    <event name="data_offer">
+      <description summary="introduce a new wp_primary_selection_offer">
+        Introduces a new wp_primary_selection_offer object that may be used
+        to receive the current primary selection. Immediately following this
+        event, the new wp_primary_selection_offer object will send
+        wp_primary_selection_offer.offer events to describe the offered mime
+        types.
+      </description>
+      <arg name="offer" type="new_id" interface="zwp_primary_selection_offer_v1"/>
+    </event>
+
+    <event name="selection">
+      <description summary="advertise a new primary selection">
+        The wp_primary_selection_device.selection event is sent to notify the
+        client of a new primary selection. This event is sent after the
+        wp_primary_selection.data_offer event introducing this object, and after
+        the offer has announced its mimetypes through
+        wp_primary_selection_offer.offer.
+
+        The data_offer is valid until a new offer or NULL is received
+        or until the client loses keyboard focus. The client must destroy the
+        previous selection data_offer, if any, upon receiving this event.
+      </description>
+      <arg name="id" type="object" interface="zwp_primary_selection_offer_v1" allow-null="true"/>
+    </event>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection device">
+        Destroy the primary selection device.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="zwp_primary_selection_offer_v1" version="1">
+    <description summary="offer to transfer primary selection contents">
+      A wp_primary_selection_offer represents an offer to transfer the contents
+      of the primary selection clipboard to the client. Similar to
+      wl_data_offer, the offer also describes the mime types that the data can
+      be converted to and provides the mechanisms for transferring the data
+      directly to the client.
+    </description>
+
+    <request name="receive">
+      <description summary="request that the data is transferred">
+        To transfer the contents of the primary selection clipboard, the client
+        issues this request and indicates the mime type that it wants to
+        receive. The transfer happens through the passed file descriptor
+        (typically created with the pipe system call). The source client writes
+        the data in the mime type representation requested and then closes the
+        file descriptor.
+
+        The receiving client reads from the read end of the pipe until EOF and
+        closes its end, at which point the transfer is complete.
+      </description>
+      <arg name="mime_type" type="string"/>
+      <arg name="fd" type="fd"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection offer">
+        Destroy the primary selection offer.
+      </description>
+    </request>
+
+    <event name="offer">
+      <description summary="advertise offered mime type">
+        Sent immediately after creating announcing the
+        wp_primary_selection_offer through
+        wp_primary_selection_device.data_offer. One event is sent per offered
+        mime type.
+      </description>
+      <arg name="mime_type" type="string"/>
+    </event>
+  </interface>
+
+  <interface name="zwp_primary_selection_source_v1" version="1">
+    <description summary="offer to replace the contents of the primary selection">
+      The source side of a wp_primary_selection_offer, it provides a way to
+      describe the offered data and respond to requests to transfer the
+      requested contents of the primary selection clipboard.
+    </description>
+
+    <request name="offer">
+      <description summary="add an offered mime type">
+        This request adds a mime type to the set of mime types advertised to
+        targets. Can be called several times to offer multiple types.
+      </description>
+      <arg name="mime_type" type="string"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection source">
+        Destroy the primary selection source.
+      </description>
+    </request>
+
+    <event name="send">
+      <description summary="send the primary selection contents">
+        Request for the current primary selection contents from the client.
+        Send the specified mime type over the passed file descriptor, then
+        close it.
+      </description>
+      <arg name="mime_type" type="string"/>
+      <arg name="fd" type="fd"/>
+    </event>
+
+    <event name="cancelled">
+      <description summary="request for primary selection contents was canceled">
+        This primary selection source is no longer valid. The client should
+        clean up and destroy this primary selection source.
+      </description>
+    </event>
+  </interface>
+</protocol>
diff --git a/src/protocol/relative-pointer-unstable-v1.xml b/src/protocol/relative-pointer-unstable-v1.xml
new file mode 100644 (file)
index 0000000..ca6f81d
--- /dev/null
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="relative_pointer_unstable_v1">
+
+  <copyright>
+    Copyright © 2014      Jonas Ådahl
+    Copyright © 2015      Red Hat Inc.
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="protocol for relative pointer motion events">
+    This protocol specifies a set of interfaces used for making clients able to
+    receive relative pointer events not obstructed by barriers (such as the
+    monitor edge or other pointer barriers).
+
+    To start receiving relative pointer events, a client must first bind the
+    global interface "wp_relative_pointer_manager" which, if a compositor
+    supports relative pointer motion events, is exposed by the registry. After
+    having created the relative pointer manager proxy object, the client uses
+    it to create the actual relative pointer object using the
+    "get_relative_pointer" request given a wl_pointer. The relative pointer
+    motion events will then, when applicable, be transmitted via the proxy of
+    the newly created relative pointer object. See the documentation of the
+    relative pointer interface for more details.
+
+    Warning! The protocol described in this file is experimental and backward
+    incompatible changes may be made. Backward compatible changes may be added
+    together with the corresponding interface version bump. Backward
+    incompatible changes are done by bumping the version number in the protocol
+    and interface names and resetting the interface version. Once the protocol
+    is to be declared stable, the 'z' prefix and the version number in the
+    protocol and interface names are removed and the interface version number is
+    reset.
+  </description>
+
+  <interface name="zwp_relative_pointer_manager_v1" version="1">
+    <description summary="get relative pointer objects">
+      A global interface used for getting the relative pointer object for a
+      given pointer.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the relative pointer manager object">
+       Used by the client to notify the server that it will no longer use this
+       relative pointer manager object.
+      </description>
+    </request>
+
+    <request name="get_relative_pointer">
+      <description summary="get a relative pointer object">
+       Create a relative pointer interface given a wl_pointer object. See the
+       wp_relative_pointer interface for more details.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_relative_pointer_v1"/>
+      <arg name="pointer" type="object" interface="wl_pointer"/>
+    </request>
+  </interface>
+
+  <interface name="zwp_relative_pointer_v1" version="1">
+    <description summary="relative pointer object">
+      A wp_relative_pointer object is an extension to the wl_pointer interface
+      used for emitting relative pointer events. It shares the same focus as
+      wl_pointer objects of the same seat and will only emit events when it has
+      focus.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="release the relative pointer object"/>
+    </request>
+
+    <event name="relative_motion">
+      <description summary="relative pointer motion">
+       Relative x/y pointer motion from the pointer of the seat associated with
+       this object.
+
+       A relative motion is in the same dimension as regular wl_pointer motion
+       events, except they do not represent an absolute position. For example,
+       moving a pointer from (x, y) to (x', y') would have the equivalent
+       relative motion (x' - x, y' - y). If a pointer motion caused the
+       absolute pointer position to be clipped by for example the edge of the
+       monitor, the relative motion is unaffected by the clipping and will
+       represent the unclipped motion.
+
+       This event also contains non-accelerated motion deltas. The
+       non-accelerated delta is, when applicable, the regular pointer motion
+       delta as it was before having applied motion acceleration and other
+       transformations such as normalization.
+
+       Note that the non-accelerated delta does not represent 'raw' events as
+       they were read from some device. Pointer motion acceleration is device-
+       and configuration-specific and non-accelerated deltas and accelerated
+       deltas may have the same value on some devices.
+
+       Relative motions are not coupled to wl_pointer.motion events, and can be
+       sent in combination with such events, but also independently. There may
+       also be scenarios where wl_pointer.motion is sent, but there is no
+       relative motion. The order of an absolute and relative motion event
+       originating from the same physical motion is not guaranteed.
+
+       If the client needs button events or focus state, it can receive them
+       from a wl_pointer object of the same seat that the wp_relative_pointer
+       object is associated with.
+      </description>
+      <arg name="utime_hi" type="uint"
+          summary="high 32 bits of a 64 bit timestamp with microsecond granularity"/>
+      <arg name="utime_lo" type="uint"
+          summary="low 32 bits of a 64 bit timestamp with microsecond granularity"/>
+      <arg name="dx" type="fixed"
+          summary="the x component of the motion vector"/>
+      <arg name="dy" type="fixed"
+          summary="the y component of the motion vector"/>
+      <arg name="dx_unaccel" type="fixed"
+          summary="the x component of the unaccelerated motion vector"/>
+      <arg name="dy_unaccel" type="fixed"
+          summary="the y component of the unaccelerated motion vector"/>
+    </event>
+  </interface>
+
+</protocol>
diff --git a/src/protocol/wayland.xml b/src/protocol/wayland.xml
new file mode 100644 (file)
index 0000000..29b63be
--- /dev/null
@@ -0,0 +1,2746 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wayland">
+
+  <copyright>
+    Copyright © 2008-2011 Kristian Høgsberg
+    Copyright © 2010-2011 Intel Corporation
+    Copyright © 2012-2013 Collabora, Ltd.
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation files
+    (the "Software"), to deal in the Software without restriction,
+    including without limitation the rights to use, copy, modify, merge,
+    publish, distribute, sublicense, and/or sell copies of the Software,
+    and to permit persons to whom the Software is furnished to do so,
+    subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the
+    next paragraph) shall be included in all copies or substantial
+    portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+    BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+    ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+    SOFTWARE.
+  </copyright>
+
+  <interface name="wl_display" version="1">
+    <description summary="core global object">
+      The core global object.  This is a special singleton object.  It
+      is used for internal Wayland protocol features.
+    </description>
+
+    <request name="sync">
+      <description summary="asynchronous roundtrip">
+       The sync request asks the server to emit the 'done' event
+       on the returned wl_callback object.  Since requests are
+       handled in-order and events are delivered in-order, this can
+       be used as a barrier to ensure all previous requests and the
+       resulting events have been handled.
+
+       The object returned by this request will be destroyed by the
+       compositor after the callback is fired and as such the client must not
+       attempt to use it after that point.
+
+       The callback_data passed in the callback is the event serial.
+      </description>
+      <arg name="callback" type="new_id" interface="wl_callback"
+          summary="callback object for the sync request"/>
+    </request>
+
+    <request name="get_registry">
+      <description summary="get global registry object">
+       This request creates a registry object that allows the client
+       to list and bind the global objects available from the
+       compositor.
+      </description>
+      <arg name="registry" type="new_id" interface="wl_registry"
+          summary="global registry object"/>
+    </request>
+
+    <event name="error">
+      <description summary="fatal error event">
+       The error event is sent out when a fatal (non-recoverable)
+       error has occurred.  The object_id argument is the object
+       where the error occurred, most often in response to a request
+       to that object.  The code identifies the error and is defined
+       by the object interface.  As such, each interface defines its
+       own set of error codes.  The message is a brief description
+       of the error, for (debugging) convenience.
+      </description>
+      <arg name="object_id" type="object" summary="object where the error occurred"/>
+      <arg name="code" type="uint" summary="error code"/>
+      <arg name="message" type="string" summary="error description"/>
+    </event>
+
+    <enum name="error">
+      <description summary="global error values">
+       These errors are global and can be emitted in response to any
+       server request.
+      </description>
+      <entry name="invalid_object" value="0"
+            summary="server couldn't find object"/>
+      <entry name="invalid_method" value="1"
+            summary="method doesn't exist on the specified interface"/>
+      <entry name="no_memory" value="2"
+            summary="server is out of memory"/>
+    </enum>
+
+    <event name="delete_id">
+      <description summary="acknowledge object ID deletion">
+       This event is used internally by the object ID management
+       logic.  When a client deletes an object, the server will send
+       this event to acknowledge that it has seen the delete request.
+       When the client receives this event, it will know that it can
+       safely reuse the object ID.
+      </description>
+      <arg name="id" type="uint" summary="deleted object ID"/>
+    </event>
+  </interface>
+
+  <interface name="wl_registry" version="1">
+    <description summary="global registry object">
+      The singleton global registry object.  The server has a number of
+      global objects that are available to all clients.  These objects
+      typically represent an actual object in the server (for example,
+      an input device) or they are singleton objects that provide
+      extension functionality.
+
+      When a client creates a registry object, the registry object
+      will emit a global event for each global currently in the
+      registry.  Globals come and go as a result of device or
+      monitor hotplugs, reconfiguration or other events, and the
+      registry will send out global and global_remove events to
+      keep the client up to date with the changes.  To mark the end
+      of the initial burst of events, the client can use the
+      wl_display.sync request immediately after calling
+      wl_display.get_registry.
+
+      A client can bind to a global object by using the bind
+      request.  This creates a client-side handle that lets the object
+      emit events to the client and lets the client invoke requests on
+      the object.
+    </description>
+
+    <request name="bind">
+      <description summary="bind an object to the display">
+       Binds a new, client-created object to the server using the
+       specified name as the identifier.
+      </description>
+      <arg name="name" type="uint" summary="unique numeric name of the object"/>
+      <arg name="id" type="new_id" summary="bounded object"/>
+    </request>
+
+    <event name="global">
+      <description summary="announce global object">
+       Notify the client of global objects.
+
+       The event notifies the client that a global object with
+       the given name is now available, and it implements the
+       given version of the given interface.
+      </description>
+      <arg name="name" type="uint" summary="numeric name of the global object"/>
+      <arg name="interface" type="string" summary="interface implemented by the object"/>
+      <arg name="version" type="uint" summary="interface version"/>
+    </event>
+
+    <event name="global_remove">
+      <description summary="announce removal of global object">
+       Notify the client of removed global objects.
+
+       This event notifies the client that the global identified
+       by name is no longer available.  If the client bound to
+       the global using the bind request, the client should now
+       destroy that object.
+
+       The object remains valid and requests to the object will be
+       ignored until the client destroys it, to avoid races between
+       the global going away and a client sending a request to it.
+      </description>
+      <arg name="name" type="uint" summary="numeric name of the global object"/>
+    </event>
+  </interface>
+
+  <interface name="wl_callback" version="1">
+    <description summary="callback object">
+      Clients can handle the 'done' event to get notified when
+      the related request is done.
+    </description>
+
+    <event name="done">
+      <description summary="done event">
+       Notify the client when the related request is done.
+      </description>
+      <arg name="callback_data" type="uint" summary="request-specific data for the callback"/>
+    </event>
+  </interface>
+
+  <interface name="wl_compositor" version="4">
+    <description summary="the compositor singleton">
+      A compositor.  This object is a singleton global.  The
+      compositor is in charge of combining the contents of multiple
+      surfaces into one displayable output.
+    </description>
+
+    <request name="create_surface">
+      <description summary="create new surface">
+       Ask the compositor to create a new surface.
+      </description>
+      <arg name="id" type="new_id" interface="wl_surface" summary="the new surface"/>
+    </request>
+
+    <request name="create_region">
+      <description summary="create new region">
+       Ask the compositor to create a new region.
+      </description>
+      <arg name="id" type="new_id" interface="wl_region" summary="the new region"/>
+    </request>
+  </interface>
+
+  <interface name="wl_shm_pool" version="1">
+    <description summary="a shared memory pool">
+      The wl_shm_pool object encapsulates a piece of memory shared
+      between the compositor and client.  Through the wl_shm_pool
+      object, the client can allocate shared memory wl_buffer objects.
+      All objects created through the same pool share the same
+      underlying mapped memory. Reusing the mapped memory avoids the
+      setup/teardown overhead and is useful when interactively resizing
+      a surface or for many small buffers.
+    </description>
+
+    <request name="create_buffer">
+      <description summary="create a buffer from the pool">
+       Create a wl_buffer object from the pool.
+
+       The buffer is created offset bytes into the pool and has
+       width and height as specified.  The stride argument specifies
+       the number of bytes from the beginning of one row to the beginning
+       of the next.  The format is the pixel format of the buffer and
+       must be one of those advertised through the wl_shm.format event.
+
+       A buffer will keep a reference to the pool it was created from
+       so it is valid to destroy the pool immediately after creating
+       a buffer from it.
+      </description>
+      <arg name="id" type="new_id" interface="wl_buffer" summary="buffer to create"/>
+      <arg name="offset" type="int" summary="buffer byte offset within the pool"/>
+      <arg name="width" type="int" summary="buffer width, in pixels"/>
+      <arg name="height" type="int" summary="buffer height, in pixels"/>
+      <arg name="stride" type="int" summary="number of bytes from the beginning of one row to the beginning of the next row"/>
+      <arg name="format" type="uint" enum="wl_shm.format" summary="buffer pixel format"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the pool">
+       Destroy the shared memory pool.
+
+       The mmapped memory will be released when all
+       buffers that have been created from this pool
+       are gone.
+      </description>
+    </request>
+
+    <request name="resize">
+      <description summary="change the size of the pool mapping">
+       This request will cause the server to remap the backing memory
+       for the pool from the file descriptor passed when the pool was
+       created, but using the new size.  This request can only be
+       used to make the pool bigger.
+      </description>
+      <arg name="size" type="int" summary="new size of the pool, in bytes"/>
+    </request>
+  </interface>
+
+  <interface name="wl_shm" version="1">
+    <description summary="shared memory support">
+      A singleton global object that provides support for shared
+      memory.
+
+      Clients can create wl_shm_pool objects using the create_pool
+      request.
+
+      At connection setup time, the wl_shm object emits one or more
+      format events to inform clients about the valid pixel formats
+      that can be used for buffers.
+    </description>
+
+    <enum name="error">
+      <description summary="wl_shm error values">
+       These errors can be emitted in response to wl_shm requests.
+      </description>
+      <entry name="invalid_format" value="0" summary="buffer format is not known"/>
+      <entry name="invalid_stride" value="1" summary="invalid size or stride during pool or buffer creation"/>
+      <entry name="invalid_fd" value="2" summary="mmapping the file descriptor failed"/>
+    </enum>
+
+    <enum name="format">
+      <description summary="pixel formats">
+       This describes the memory layout of an individual pixel.
+
+       All renderers should support argb8888 and xrgb8888 but any other
+       formats are optional and may not be supported by the particular
+       renderer in use.
+
+       The drm format codes match the macros defined in drm_fourcc.h.
+       The formats actually supported by the compositor will be
+       reported by the format event.
+      </description>
+      <entry name="argb8888" value="0" summary="32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian"/>
+      <entry name="xrgb8888" value="1" summary="32-bit RGB format, [31:0] x:R:G:B 8:8:8:8 little endian"/>
+      <entry name="c8" value="0x20203843" summary="8-bit color index format, [7:0] C"/>
+      <entry name="rgb332" value="0x38424752" summary="8-bit RGB format, [7:0] R:G:B 3:3:2"/>
+      <entry name="bgr233" value="0x38524742" summary="8-bit BGR format, [7:0] B:G:R 2:3:3"/>
+      <entry name="xrgb4444" value="0x32315258" summary="16-bit xRGB format, [15:0] x:R:G:B 4:4:4:4 little endian"/>
+      <entry name="xbgr4444" value="0x32314258" summary="16-bit xBGR format, [15:0] x:B:G:R 4:4:4:4 little endian"/>
+      <entry name="rgbx4444" value="0x32315852" summary="16-bit RGBx format, [15:0] R:G:B:x 4:4:4:4 little endian"/>
+      <entry name="bgrx4444" value="0x32315842" summary="16-bit BGRx format, [15:0] B:G:R:x 4:4:4:4 little endian"/>
+      <entry name="argb4444" value="0x32315241" summary="16-bit ARGB format, [15:0] A:R:G:B 4:4:4:4 little endian"/>
+      <entry name="abgr4444" value="0x32314241" summary="16-bit ABGR format, [15:0] A:B:G:R 4:4:4:4 little endian"/>
+      <entry name="rgba4444" value="0x32314152" summary="16-bit RBGA format, [15:0] R:G:B:A 4:4:4:4 little endian"/>
+      <entry name="bgra4444" value="0x32314142" summary="16-bit BGRA format, [15:0] B:G:R:A 4:4:4:4 little endian"/>
+      <entry name="xrgb1555" value="0x35315258" summary="16-bit xRGB format, [15:0] x:R:G:B 1:5:5:5 little endian"/>
+      <entry name="xbgr1555" value="0x35314258" summary="16-bit xBGR 1555 format, [15:0] x:B:G:R 1:5:5:5 little endian"/>
+      <entry name="rgbx5551" value="0x35315852" summary="16-bit RGBx 5551 format, [15:0] R:G:B:x 5:5:5:1 little endian"/>
+      <entry name="bgrx5551" value="0x35315842" summary="16-bit BGRx 5551 format, [15:0] B:G:R:x 5:5:5:1 little endian"/>
+      <entry name="argb1555" value="0x35315241" summary="16-bit ARGB 1555 format, [15:0] A:R:G:B 1:5:5:5 little endian"/>
+      <entry name="abgr1555" value="0x35314241" summary="16-bit ABGR 1555 format, [15:0] A:B:G:R 1:5:5:5 little endian"/>
+      <entry name="rgba5551" value="0x35314152" summary="16-bit RGBA 5551 format, [15:0] R:G:B:A 5:5:5:1 little endian"/>
+      <entry name="bgra5551" value="0x35314142" summary="16-bit BGRA 5551 format, [15:0] B:G:R:A 5:5:5:1 little endian"/>
+      <entry name="rgb565" value="0x36314752" summary="16-bit RGB 565 format, [15:0] R:G:B 5:6:5 little endian"/>
+      <entry name="bgr565" value="0x36314742" summary="16-bit BGR 565 format, [15:0] B:G:R 5:6:5 little endian"/>
+      <entry name="rgb888" value="0x34324752" summary="24-bit RGB format, [23:0] R:G:B little endian"/>
+      <entry name="bgr888" value="0x34324742" summary="24-bit BGR format, [23:0] B:G:R little endian"/>
+      <entry name="xbgr8888" value="0x34324258" summary="32-bit xBGR format, [31:0] x:B:G:R 8:8:8:8 little endian"/>
+      <entry name="rgbx8888" value="0x34325852" summary="32-bit RGBx format, [31:0] R:G:B:x 8:8:8:8 little endian"/>
+      <entry name="bgrx8888" value="0x34325842" summary="32-bit BGRx format, [31:0] B:G:R:x 8:8:8:8 little endian"/>
+      <entry name="abgr8888" value="0x34324241" summary="32-bit ABGR format, [31:0] A:B:G:R 8:8:8:8 little endian"/>
+      <entry name="rgba8888" value="0x34324152" summary="32-bit RGBA format, [31:0] R:G:B:A 8:8:8:8 little endian"/>
+      <entry name="bgra8888" value="0x34324142" summary="32-bit BGRA format, [31:0] B:G:R:A 8:8:8:8 little endian"/>
+      <entry name="xrgb2101010" value="0x30335258" summary="32-bit xRGB format, [31:0] x:R:G:B 2:10:10:10 little endian"/>
+      <entry name="xbgr2101010" value="0x30334258" summary="32-bit xBGR format, [31:0] x:B:G:R 2:10:10:10 little endian"/>
+      <entry name="rgbx1010102" value="0x30335852" summary="32-bit RGBx format, [31:0] R:G:B:x 10:10:10:2 little endian"/>
+      <entry name="bgrx1010102" value="0x30335842" summary="32-bit BGRx format, [31:0] B:G:R:x 10:10:10:2 little endian"/>
+      <entry name="argb2101010" value="0x30335241" summary="32-bit ARGB format, [31:0] A:R:G:B 2:10:10:10 little endian"/>
+      <entry name="abgr2101010" value="0x30334241" summary="32-bit ABGR format, [31:0] A:B:G:R 2:10:10:10 little endian"/>
+      <entry name="rgba1010102" value="0x30334152" summary="32-bit RGBA format, [31:0] R:G:B:A 10:10:10:2 little endian"/>
+      <entry name="bgra1010102" value="0x30334142" summary="32-bit BGRA format, [31:0] B:G:R:A 10:10:10:2 little endian"/>
+      <entry name="yuyv" value="0x56595559" summary="packed YCbCr format, [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian"/>
+      <entry name="yvyu" value="0x55595659" summary="packed YCbCr format, [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian"/>
+      <entry name="uyvy" value="0x59565955" summary="packed YCbCr format, [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian"/>
+      <entry name="vyuy" value="0x59555956" summary="packed YCbCr format, [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian"/>
+      <entry name="ayuv" value="0x56555941" summary="packed AYCbCr format, [31:0] A:Y:Cb:Cr 8:8:8:8 little endian"/>
+      <entry name="nv12" value="0x3231564e" summary="2 plane YCbCr Cr:Cb format, 2x2 subsampled Cr:Cb plane"/>
+      <entry name="nv21" value="0x3132564e" summary="2 plane YCbCr Cb:Cr format, 2x2 subsampled Cb:Cr plane"/>
+      <entry name="nv16" value="0x3631564e" summary="2 plane YCbCr Cr:Cb format, 2x1 subsampled Cr:Cb plane"/>
+      <entry name="nv61" value="0x3136564e" summary="2 plane YCbCr Cb:Cr format, 2x1 subsampled Cb:Cr plane"/>
+      <entry name="yuv410" value="0x39565559" summary="3 plane YCbCr format, 4x4 subsampled Cb (1) and Cr (2) planes"/>
+      <entry name="yvu410" value="0x39555659" summary="3 plane YCbCr format, 4x4 subsampled Cr (1) and Cb (2) planes"/>
+      <entry name="yuv411" value="0x31315559" summary="3 plane YCbCr format, 4x1 subsampled Cb (1) and Cr (2) planes"/>
+      <entry name="yvu411" value="0x31315659" summary="3 plane YCbCr format, 4x1 subsampled Cr (1) and Cb (2) planes"/>
+      <entry name="yuv420" value="0x32315559" summary="3 plane YCbCr format, 2x2 subsampled Cb (1) and Cr (2) planes"/>
+      <entry name="yvu420" value="0x32315659" summary="3 plane YCbCr format, 2x2 subsampled Cr (1) and Cb (2) planes"/>
+      <entry name="yuv422" value="0x36315559" summary="3 plane YCbCr format, 2x1 subsampled Cb (1) and Cr (2) planes"/>
+      <entry name="yvu422" value="0x36315659" summary="3 plane YCbCr format, 2x1 subsampled Cr (1) and Cb (2) planes"/>
+      <entry name="yuv444" value="0x34325559" summary="3 plane YCbCr format, non-subsampled Cb (1) and Cr (2) planes"/>
+      <entry name="yvu444" value="0x34325659" summary="3 plane YCbCr format, non-subsampled Cr (1) and Cb (2) planes"/>
+    </enum>
+
+    <request name="create_pool">
+      <description summary="create a shm pool">
+       Create a new wl_shm_pool object.
+
+       The pool can be used to create shared memory based buffer
+       objects.  The server will mmap size bytes of the passed file
+       descriptor, to use as backing memory for the pool.
+      </description>
+      <arg name="id" type="new_id" interface="wl_shm_pool" summary="pool to create"/>
+      <arg name="fd" type="fd" summary="file descriptor for the pool"/>
+      <arg name="size" type="int" summary="pool size, in bytes"/>
+    </request>
+
+    <event name="format">
+      <description summary="pixel format description">
+       Informs the client about a valid pixel format that
+       can be used for buffers. Known formats include
+       argb8888 and xrgb8888.
+      </description>
+      <arg name="format" type="uint" enum="format" summary="buffer pixel format"/>
+    </event>
+  </interface>
+
+  <interface name="wl_buffer" version="1">
+    <description summary="content for a wl_surface">
+      A buffer provides the content for a wl_surface. Buffers are
+      created through factory interfaces such as wl_drm, wl_shm or
+      similar. It has a width and a height and can be attached to a
+      wl_surface, but the mechanism by which a client provides and
+      updates the contents is defined by the buffer factory interface.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy a buffer">
+       Destroy a buffer. If and how you need to release the backing
+       storage is defined by the buffer factory interface.
+
+       For possible side-effects to a surface, see wl_surface.attach.
+      </description>
+    </request>
+
+    <event name="release">
+      <description summary="compositor releases buffer">
+       Sent when this wl_buffer is no longer used by the compositor.
+       The client is now free to reuse or destroy this buffer and its
+       backing storage.
+
+       If a client receives a release event before the frame callback
+       requested in the same wl_surface.commit that attaches this
+       wl_buffer to a surface, then the client is immediately free to
+       reuse the buffer and its backing storage, and does not need a
+       second buffer for the next surface content update. Typically
+       this is possible, when the compositor maintains a copy of the
+       wl_surface contents, e.g. as a GL texture. This is an important
+       optimization for GL(ES) compositors with wl_shm clients.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="wl_data_offer" version="3">
+    <description summary="offer to transfer data">
+      A wl_data_offer represents a piece of data offered for transfer
+      by another client (the source client).  It is used by the
+      copy-and-paste and drag-and-drop mechanisms.  The offer
+      describes the different mime types that the data can be
+      converted to and provides the mechanism for transferring the
+      data directly from the source client.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_finish" value="0"
+            summary="finish request was called untimely"/>
+      <entry name="invalid_action_mask" value="1"
+            summary="action mask contains invalid values"/>
+      <entry name="invalid_action" value="2"
+            summary="action argument has an invalid value"/>
+      <entry name="invalid_offer" value="3"
+            summary="offer doesn't accept this request"/>
+    </enum>
+
+    <request name="accept">
+      <description summary="accept one of the offered mime types">
+       Indicate that the client can accept the given mime type, or
+       NULL for not accepted.
+
+       For objects of version 2 or older, this request is used by the
+       client to give feedback whether the client can receive the given
+       mime type, or NULL if none is accepted; the feedback does not
+       determine whether the drag-and-drop operation succeeds or not.
+
+       For objects of version 3 or newer, this request determines the
+       final result of the drag-and-drop operation. If the end result
+       is that no mime types were accepted, the drag-and-drop operation
+       will be cancelled and the corresponding drag source will receive
+       wl_data_source.cancelled. Clients may still use this event in
+       conjunction with wl_data_source.action for feedback.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the accept request"/>
+      <arg name="mime_type" type="string" allow-null="true" summary="mime type accepted by the client"/>
+    </request>
+
+    <request name="receive">
+      <description summary="request that the data is transferred">
+       To transfer the offered data, the client issues this request
+       and indicates the mime type it wants to receive.  The transfer
+       happens through the passed file descriptor (typically created
+       with the pipe system call).  The source client writes the data
+       in the mime type representation requested and then closes the
+       file descriptor.
+
+       The receiving client reads from the read end of the pipe until
+       EOF and then closes its end, at which point the transfer is
+       complete.
+
+       This request may happen multiple times for different mime types,
+       both before and after wl_data_device.drop. Drag-and-drop destination
+       clients may preemptively fetch data or examine it more closely to
+       determine acceptance.
+      </description>
+      <arg name="mime_type" type="string" summary="mime type desired by receiver"/>
+      <arg name="fd" type="fd" summary="file descriptor for data transfer"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy data offer">
+       Destroy the data offer.
+      </description>
+    </request>
+
+    <event name="offer">
+      <description summary="advertise offered mime type">
+       Sent immediately after creating the wl_data_offer object.  One
+       event per offered mime type.
+      </description>
+      <arg name="mime_type" type="string" summary="offered mime type"/>
+    </event>
+
+    <!-- Version 3 additions -->
+
+    <request name="finish" since="3">
+      <description summary="the offer will no longer be used">
+       Notifies the compositor that the drag destination successfully
+       finished the drag-and-drop operation.
+
+       Upon receiving this request, the compositor will emit
+       wl_data_source.dnd_finished on the drag source client.
+
+       It is a client error to perform other requests than
+       wl_data_offer.destroy after this one. It is also an error to perform
+       this request after a NULL mime type has been set in
+       wl_data_offer.accept or no action was received through
+       wl_data_offer.action.
+      </description>
+    </request>
+
+    <request name="set_actions" since="3">
+      <description summary="set the available/preferred drag-and-drop actions">
+       Sets the actions that the destination side client supports for
+       this operation. This request may trigger the emission of
+       wl_data_source.action and wl_data_offer.action events if the compositor
+       needs to change the selected action.
+
+       This request can be called multiple times throughout the
+       drag-and-drop operation, typically in response to wl_data_device.enter
+       or wl_data_device.motion events.
+
+       This request determines the final result of the drag-and-drop
+       operation. If the end result is that no action is accepted,
+       the drag source will receive wl_drag_source.cancelled.
+
+       The dnd_actions argument must contain only values expressed in the
+       wl_data_device_manager.dnd_actions enum, and the preferred_action
+       argument must only contain one of those values set, otherwise it
+       will result in a protocol error.
+
+       While managing an "ask" action, the destination drag-and-drop client
+       may perform further wl_data_offer.receive requests, and is expected
+       to perform one last wl_data_offer.set_actions request with a preferred
+       action other than "ask" (and optionally wl_data_offer.accept) before
+       requesting wl_data_offer.finish, in order to convey the action selected
+       by the user. If the preferred action is not in the
+       wl_data_offer.source_actions mask, an error will be raised.
+
+       If the "ask" action is dismissed (e.g. user cancellation), the client
+       is expected to perform wl_data_offer.destroy right away.
+
+       This request can only be made on drag-and-drop offers, a protocol error
+       will be raised otherwise.
+      </description>
+      <arg name="dnd_actions" type="uint" summary="actions supported by the destination client"/>
+      <arg name="preferred_action" type="uint" summary="action preferred by the destination client"/>
+    </request>
+
+    <event name="source_actions" since="3">
+      <description summary="notify the source-side available actions">
+       This event indicates the actions offered by the data source. It
+       will be sent right after wl_data_device.enter, or anytime the source
+       side changes its offered actions through wl_data_source.set_actions.
+      </description>
+      <arg name="source_actions" type="uint" summary="actions offered by the data source"/>
+    </event>
+
+    <event name="action" since="3">
+      <description summary="notify the selected action">
+       This event indicates the action selected by the compositor after
+       matching the source/destination side actions. Only one action (or
+       none) will be offered here.
+
+       This event can be emitted multiple times during the drag-and-drop
+       operation in response to destination side action changes through
+       wl_data_offer.set_actions.
+
+       This event will no longer be emitted after wl_data_device.drop
+       happened on the drag-and-drop destination, the client must
+       honor the last action received, or the last preferred one set
+       through wl_data_offer.set_actions when handling an "ask" action.
+
+       Compositors may also change the selected action on the fly, mainly
+       in response to keyboard modifier changes during the drag-and-drop
+       operation.
+
+       The most recent action received is always the valid one. Prior to
+       receiving wl_data_device.drop, the chosen action may change (e.g.
+       due to keyboard modifiers being pressed). At the time of receiving
+       wl_data_device.drop the drag-and-drop destination must honor the
+       last action received.
+
+       Action changes may still happen after wl_data_device.drop,
+       especially on "ask" actions, where the drag-and-drop destination
+       may choose another action afterwards. Action changes happening
+       at this stage are always the result of inter-client negotiation, the
+       compositor shall no longer be able to induce a different action.
+
+       Upon "ask" actions, it is expected that the drag-and-drop destination
+       may potentially choose a different action and/or mime type,
+       based on wl_data_offer.source_actions and finally chosen by the
+       user (e.g. popping up a menu with the available options). The
+       final wl_data_offer.set_actions and wl_data_offer.accept requests
+       must happen before the call to wl_data_offer.finish.
+      </description>
+      <arg name="dnd_action" type="uint" summary="action selected by the compositor"/>
+    </event>
+  </interface>
+
+  <interface name="wl_data_source" version="3">
+    <description summary="offer to transfer data">
+      The wl_data_source object is the source side of a wl_data_offer.
+      It is created by the source client in a data transfer and
+      provides a way to describe the offered data and a way to respond
+      to requests to transfer the data.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_action_mask" value="0"
+            summary="action mask contains invalid values"/>
+      <entry name="invalid_source" value="1"
+            summary="source doesn't accept this request"/>
+    </enum>
+
+    <request name="offer">
+      <description summary="add an offered mime type">
+       This request adds a mime type to the set of mime types
+       advertised to targets.  Can be called several times to offer
+       multiple types.
+      </description>
+      <arg name="mime_type" type="string" summary="mime type offered by the data source"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the data source">
+       Destroy the data source.
+      </description>
+    </request>
+
+    <event name="target">
+      <description summary="a target accepts an offered mime type">
+       Sent when a target accepts pointer_focus or motion events.  If
+       a target does not accept any of the offered types, type is NULL.
+
+       Used for feedback during drag-and-drop.
+      </description>
+      <arg name="mime_type" type="string" allow-null="true" summary="mime type accepted by the target"/>
+    </event>
+
+    <event name="send">
+      <description summary="send the data">
+       Request for data from the client.  Send the data as the
+       specified mime type over the passed file descriptor, then
+       close it.
+      </description>
+      <arg name="mime_type" type="string" summary="mime type for the data"/>
+      <arg name="fd" type="fd" summary="file descriptor for the data"/>
+    </event>
+
+    <event name="cancelled">
+      <description summary="selection was cancelled">
+       This data source is no longer valid. There are several reasons why
+       this could happen:
+
+       - The data source has been replaced by another data source.
+       - The drag-and-drop operation was performed, but the drop destination
+         did not accept any of the mime types offered through
+         wl_data_source.target.
+       - The drag-and-drop operation was performed, but the drop destination
+         did not select any of the actions present in the mask offered through
+         wl_data_source.action.
+       - The drag-and-drop operation was performed but didn't happen over a
+         surface.
+       - The compositor cancelled the drag-and-drop operation (e.g. compositor
+         dependent timeouts to avoid stale drag-and-drop transfers).
+
+       The client should clean up and destroy this data source.
+
+       For objects of version 2 or older, wl_data_source.cancelled will
+       only be emitted if the data source was replaced by another data
+       source.
+      </description>
+    </event>
+
+    <!-- Version 3 additions -->
+
+    <request name="set_actions" since="3">
+      <description summary="set the available drag-and-drop actions">
+       Sets the actions that the source side client supports for this
+       operation. This request may trigger wl_data_source.action and
+       wl_data_offer.action events if the compositor needs to change the
+       selected action.
+
+       The dnd_actions argument must contain only values expressed in the
+       wl_data_device_manager.dnd_actions enum, otherwise it will result
+       in a protocol error.
+
+       This request must be made once only, and can only be made on sources
+       used in drag-and-drop, so it must be performed before
+       wl_data_device.start_drag. Attempting to use the source other than
+       for drag-and-drop will raise a protocol error.
+      </description>
+      <arg name="dnd_actions" type="uint" summary="actions supported by the data source"/>
+    </request>
+
+    <event name="dnd_drop_performed" since="3">
+      <description summary="the drag-and-drop operation physically finished">
+       The user performed the drop action. This event does not indicate
+       acceptance, wl_data_source.cancelled may still be emitted afterwards
+       if the drop destination does not accept any mime type.
+
+       However, this event might however not be received if the compositor
+       cancelled the drag-and-drop operation before this event could happen.
+
+       Note that the data_source may still be used in the future and should
+       not be destroyed here.
+      </description>
+    </event>
+
+    <event name="dnd_finished" since="3">
+      <description summary="the drag-and-drop operation concluded">
+       The drop destination finished interoperating with this data
+       source, so the client is now free to destroy this data source and
+       free all associated data.
+
+       If the action used to perform the operation was "move", the
+       source can now delete the transferred data.
+      </description>
+    </event>
+
+    <event name="action" since="3">
+      <description summary="notify the selected action">
+       This event indicates the action selected by the compositor after
+       matching the source/destination side actions. Only one action (or
+       none) will be offered here.
+
+       This event can be emitted multiple times during the drag-and-drop
+       operation, mainly in response to destination side changes through
+       wl_data_offer.set_actions, and as the data device enters/leaves
+       surfaces.
+
+       It is only possible to receive this event after
+       wl_data_source.dnd_drop_performed if the drag-and-drop operation
+       ended in an "ask" action, in which case the final wl_data_source.action
+       event will happen immediately before wl_data_source.dnd_finished.
+
+       Compositors may also change the selected action on the fly, mainly
+       in response to keyboard modifier changes during the drag-and-drop
+       operation.
+
+       The most recent action received is always the valid one. The chosen
+       action may change alongside negotiation (e.g. an "ask" action can turn
+       into a "move" operation), so the effects of the final action must
+       always be applied in wl_data_offer.dnd_finished.
+
+       Clients can trigger cursor surface changes from this point, so
+       they reflect the current action.
+      </description>
+      <arg name="dnd_action" type="uint" summary="action selected by the compositor"/>
+    </event>
+  </interface>
+
+  <interface name="wl_data_device" version="3">
+    <description summary="data transfer device">
+      There is one wl_data_device per seat which can be obtained
+      from the global wl_data_device_manager singleton.
+
+      A wl_data_device provides access to inter-client data transfer
+      mechanisms such as copy-and-paste and drag-and-drop.
+    </description>
+
+    <enum name="error">
+      <entry name="role" value="0" summary="given wl_surface has another role"/>
+    </enum>
+
+    <request name="start_drag">
+      <description summary="start drag-and-drop operation">
+       This request asks the compositor to start a drag-and-drop
+       operation on behalf of the client.
+
+       The source argument is the data source that provides the data
+       for the eventual data transfer. If source is NULL, enter, leave
+       and motion events are sent only to the client that initiated the
+       drag and the client is expected to handle the data passing
+       internally.
+
+       The origin surface is the surface where the drag originates and
+       the client must have an active implicit grab that matches the
+       serial.
+
+       The icon surface is an optional (can be NULL) surface that
+       provides an icon to be moved around with the cursor.  Initially,
+       the top-left corner of the icon surface is placed at the cursor
+       hotspot, but subsequent wl_surface.attach request can move the
+       relative position. Attach requests must be confirmed with
+       wl_surface.commit as usual. The icon surface is given the role of
+       a drag-and-drop icon. If the icon surface already has another role,
+       it raises a protocol error.
+
+       The current and pending input regions of the icon wl_surface are
+       cleared, and wl_surface.set_input_region is ignored until the
+       wl_surface is no longer used as the icon surface. When the use
+       as an icon ends, the current and pending input regions become
+       undefined, and the wl_surface is unmapped.
+      </description>
+      <arg name="source" type="object" interface="wl_data_source" allow-null="true" summary="data source for the eventual transfer"/>
+      <arg name="origin" type="object" interface="wl_surface" summary="surface where the drag originates"/>
+      <arg name="icon" type="object" interface="wl_surface" allow-null="true" summary="drag-and-drop icon surface"/>
+      <arg name="serial" type="uint" summary="serial number of the implicit grab on the origin"/>
+    </request>
+
+    <request name="set_selection">
+      <description summary="copy data to the selection">
+       This request asks the compositor to set the selection
+       to the data from the source on behalf of the client.
+
+       To unset the selection, set the source to NULL.
+      </description>
+      <arg name="source" type="object" interface="wl_data_source" allow-null="true" summary="data source for the selection"/>
+      <arg name="serial" type="uint" summary="serial number of the event that triggered this request"/>
+    </request>
+
+    <event name="data_offer">
+      <description summary="introduce a new wl_data_offer">
+       The data_offer event introduces a new wl_data_offer object,
+       which will subsequently be used in either the
+       data_device.enter event (for drag-and-drop) or the
+       data_device.selection event (for selections).  Immediately
+       following the data_device_data_offer event, the new data_offer
+       object will send out data_offer.offer events to describe the
+       mime types it offers.
+      </description>
+      <arg name="id" type="new_id" interface="wl_data_offer" summary="the new data_offer object"/>
+    </event>
+
+    <event name="enter">
+      <description summary="initiate drag-and-drop session">
+       This event is sent when an active drag-and-drop pointer enters
+       a surface owned by the client.  The position of the pointer at
+       enter time is provided by the x and y arguments, in surface-local
+       coordinates.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the enter event"/>
+      <arg name="surface" type="object" interface="wl_surface" summary="client surface entered"/>
+      <arg name="x" type="fixed" summary="surface-local x coordinate"/>
+      <arg name="y" type="fixed" summary="surface-local y coordinate"/>
+      <arg name="id" type="object" interface="wl_data_offer" allow-null="true"
+          summary="source data_offer object"/>
+    </event>
+
+    <event name="leave">
+      <description summary="end drag-and-drop session">
+       This event is sent when the drag-and-drop pointer leaves the
+       surface and the session ends.  The client must destroy the
+       wl_data_offer introduced at enter time at this point.
+      </description>
+    </event>
+
+    <event name="motion">
+      <description summary="drag-and-drop session motion">
+       This event is sent when the drag-and-drop pointer moves within
+       the currently focused surface. The new position of the pointer
+       is provided by the x and y arguments, in surface-local
+       coordinates.
+      </description>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="x" type="fixed" summary="surface-local x coordinate"/>
+      <arg name="y" type="fixed" summary="surface-local y coordinate"/>
+    </event>
+
+    <event name="drop">
+      <description summary="end drag-and-drop session successfully">
+       The event is sent when a drag-and-drop operation is ended
+       because the implicit grab is removed.
+
+       The drag-and-drop destination is expected to honor the last action
+       received through wl_data_offer.action, if the resulting action is
+       "copy" or "move", the destination can still perform
+       wl_data_offer.receive requests, and is expected to end all
+       transfers with a wl_data_offer.finish request.
+
+       If the resulting action is "ask", the action will not be considered
+       final. The drag-and-drop destination is expected to perform one last
+       wl_data_offer.set_actions request, or wl_data_offer.destroy in order
+       to cancel the operation.
+      </description>
+    </event>
+
+    <event name="selection">
+      <description summary="advertise new selection">
+       The selection event is sent out to notify the client of a new
+       wl_data_offer for the selection for this device.  The
+       data_device.data_offer and the data_offer.offer events are
+       sent out immediately before this event to introduce the data
+       offer object.  The selection event is sent to a client
+       immediately before receiving keyboard focus and when a new
+       selection is set while the client has keyboard focus.  The
+       data_offer is valid until a new data_offer or NULL is received
+       or until the client loses keyboard focus.  The client must
+       destroy the previous selection data_offer, if any, upon receiving
+       this event.
+      </description>
+      <arg name="id" type="object" interface="wl_data_offer" allow-null="true"
+          summary="selection data_offer object"/>
+    </event>
+
+    <!-- Version 2 additions -->
+
+    <request name="release" type="destructor" since="2">
+      <description summary="destroy data device">
+       This request destroys the data device.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="wl_data_device_manager" version="3">
+    <description summary="data transfer interface">
+      The wl_data_device_manager is a singleton global object that
+      provides access to inter-client data transfer mechanisms such as
+      copy-and-paste and drag-and-drop.  These mechanisms are tied to
+      a wl_seat and this interface lets a client get a wl_data_device
+      corresponding to a wl_seat.
+
+      Depending on the version bound, the objects created from the bound
+      wl_data_device_manager object will have different requirements for
+      functioning properly. See wl_data_source.set_actions,
+      wl_data_offer.accept and wl_data_offer.finish for details.
+    </description>
+
+    <request name="create_data_source">
+      <description summary="create a new data source">
+       Create a new data source.
+      </description>
+      <arg name="id" type="new_id" interface="wl_data_source" summary="data source to create"/>
+    </request>
+
+    <request name="get_data_device">
+      <description summary="create a new data device">
+       Create a new data device for a given seat.
+      </description>
+      <arg name="id" type="new_id" interface="wl_data_device" summary="data device to create"/>
+      <arg name="seat" type="object" interface="wl_seat" summary="seat associated with the data device"/>
+    </request>
+
+    <!-- Version 3 additions -->
+
+    <enum name="dnd_action" bitfield="true" since="3">
+      <description summary="drag and drop actions">
+       This is a bitmask of the available/preferred actions in a
+       drag-and-drop operation.
+
+       In the compositor, the selected action is a result of matching the
+       actions offered by the source and destination sides.  "action" events
+       with a "none" action will be sent to both source and destination if
+       there is no match. All further checks will effectively happen on
+       (source actions ∩ destination actions).
+
+       In addition, compositors may also pick different actions in
+       reaction to key modifiers being pressed. One common design that
+       is used in major toolkits (and the behavior recommended for
+       compositors) is:
+
+       - If no modifiers are pressed, the first match (in bit order)
+         will be used.
+       - Pressing Shift selects "move", if enabled in the mask.
+       - Pressing Control selects "copy", if enabled in the mask.
+
+       Behavior beyond that is considered implementation-dependent.
+       Compositors may for example bind other modifiers (like Alt/Meta)
+       or drags initiated with other buttons than BTN_LEFT to specific
+       actions (e.g. "ask").
+      </description>
+      <entry name="none" value="0" summary="no action"/>
+      <entry name="copy" value="1" summary="copy action"/>
+      <entry name="move" value="2" summary="move action"/>
+      <entry name="ask" value="4" summary="ask action"/>
+    </enum>
+  </interface>
+
+  <interface name="wl_shell" version="1">
+    <description summary="create desktop-style surfaces">
+      This interface is implemented by servers that provide
+      desktop-style user interfaces.
+
+      It allows clients to associate a wl_shell_surface with
+      a basic surface.
+    </description>
+
+    <enum name="error">
+      <entry name="role" value="0" summary="given wl_surface has another role"/>
+    </enum>
+
+    <request name="get_shell_surface">
+      <description summary="create a shell surface from a surface">
+       Create a shell surface for an existing surface. This gives
+       the wl_surface the role of a shell surface. If the wl_surface
+       already has another role, it raises a protocol error.
+
+       Only one shell surface can be associated with a given surface.
+      </description>
+      <arg name="id" type="new_id" interface="wl_shell_surface" summary="shell surface to create"/>
+      <arg name="surface" type="object" interface="wl_surface" summary="surface to be given the shell surface role"/>
+    </request>
+  </interface>
+
+  <interface name="wl_shell_surface" version="1">
+    <description summary="desktop-style metadata interface">
+      An interface that may be implemented by a wl_surface, for
+      implementations that provide a desktop-style user interface.
+
+      It provides requests to treat surfaces like toplevel, fullscreen
+      or popup windows, move, resize or maximize them, associate
+      metadata like title and class, etc.
+
+      On the server side the object is automatically destroyed when
+      the related wl_surface is destroyed. On the client side,
+      wl_shell_surface_destroy() must be called before destroying
+      the wl_surface object.
+    </description>
+
+    <request name="pong">
+      <description summary="respond to a ping event">
+       A client must respond to a ping event with a pong request or
+       the client may be deemed unresponsive.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the ping event"/>
+    </request>
+
+    <request name="move">
+      <description summary="start an interactive move">
+       Start a pointer-driven move of the surface.
+
+       This request must be used in response to a button press event.
+       The server may ignore move requests depending on the state of
+       the surface (e.g. fullscreen or maximized).
+      </description>
+      <arg name="seat" type="object" interface="wl_seat" summary="seat whose pointer is used"/>
+      <arg name="serial" type="uint" summary="serial number of the implicit grab on the pointer"/>
+    </request>
+
+    <enum name="resize" bitfield="true">
+      <description summary="edge values for resizing">
+       These values are used to indicate which edge of a surface
+       is being dragged in a resize operation. The server may
+       use this information to adapt its behavior, e.g. choose
+       an appropriate cursor image.
+      </description>
+      <entry name="none" value="0" summary="no edge"/>
+      <entry name="top" value="1" summary="top edge"/>
+      <entry name="bottom" value="2" summary="bottom edge"/>
+      <entry name="left" value="4" summary="left edge"/>
+      <entry name="top_left" value="5" summary="top and left edges"/>
+      <entry name="bottom_left" value="6" summary="bottom and left edges"/>
+      <entry name="right" value="8" summary="right edge"/>
+      <entry name="top_right" value="9" summary="top and right edges"/>
+      <entry name="bottom_right" value="10" summary="bottom and right edges"/>
+    </enum>
+
+    <request name="resize">
+      <description summary="start an interactive resize">
+       Start a pointer-driven resizing of the surface.
+
+       This request must be used in response to a button press event.
+       The server may ignore resize requests depending on the state of
+       the surface (e.g. fullscreen or maximized).
+      </description>
+      <arg name="seat" type="object" interface="wl_seat" summary="seat whose pointer is used"/>
+      <arg name="serial" type="uint" summary="serial number of the implicit grab on the pointer"/>
+      <arg name="edges" type="uint" enum="resize" summary="which edge or corner is being dragged"/>
+    </request>
+
+    <request name="set_toplevel">
+      <description summary="make the surface a toplevel surface">
+       Map the surface as a toplevel surface.
+
+       A toplevel surface is not fullscreen, maximized or transient.
+      </description>
+    </request>
+
+    <enum name="transient" bitfield="true">
+      <description summary="details of transient behaviour">
+       These flags specify details of the expected behaviour
+       of transient surfaces. Used in the set_transient request.
+      </description>
+      <entry name="inactive" value="0x1" summary="do not set keyboard focus"/>
+    </enum>
+
+    <request name="set_transient">
+      <description summary="make the surface a transient surface">
+       Map the surface relative to an existing surface.
+
+       The x and y arguments specify the location of the upper left
+       corner of the surface relative to the upper left corner of the
+       parent surface, in surface-local coordinates.
+
+       The flags argument controls details of the transient behaviour.
+      </description>
+      <arg name="parent" type="object" interface="wl_surface" summary="parent surface"/>
+      <arg name="x" type="int" summary="surface-local x coordinate"/>
+      <arg name="y" type="int" summary="surface-local y coordinate"/>
+      <arg name="flags" type="uint" enum="transient" summary="transient surface behavior"/>
+    </request>
+
+    <enum name="fullscreen_method">
+      <description summary="different method to set the surface fullscreen">
+       Hints to indicate to the compositor how to deal with a conflict
+       between the dimensions of the surface and the dimensions of the
+       output. The compositor is free to ignore this parameter.
+      </description>
+      <entry name="default" value="0" summary="no preference, apply default policy"/>
+      <entry name="scale" value="1" summary="scale, preserve the surface's aspect ratio and center on output"/>
+      <entry name="driver" value="2" summary="switch output mode to the smallest mode that can fit the surface, add black borders to compensate size mismatch"/>
+      <entry name="fill" value="3" summary="no upscaling, center on output and add black borders to compensate size mismatch"/>
+    </enum>
+
+    <request name="set_fullscreen">
+      <description summary="make the surface a fullscreen surface">
+       Map the surface as a fullscreen surface.
+
+       If an output parameter is given then the surface will be made
+       fullscreen on that output. If the client does not specify the
+       output then the compositor will apply its policy - usually
+       choosing the output on which the surface has the biggest surface
+       area.
+
+       The client may specify a method to resolve a size conflict
+       between the output size and the surface size - this is provided
+       through the method parameter.
+
+       The framerate parameter is used only when the method is set
+       to "driver", to indicate the preferred framerate. A value of 0
+       indicates that the client does not care about framerate.  The
+       framerate is specified in mHz, that is framerate of 60000 is 60Hz.
+
+       A method of "scale" or "driver" implies a scaling operation of
+       the surface, either via a direct scaling operation or a change of
+       the output mode. This will override any kind of output scaling, so
+       that mapping a surface with a buffer size equal to the mode can
+       fill the screen independent of buffer_scale.
+
+       A method of "fill" means we don't scale up the buffer, however
+       any output scale is applied. This means that you may run into
+       an edge case where the application maps a buffer with the same
+       size of the output mode but buffer_scale 1 (thus making a
+       surface larger than the output). In this case it is allowed to
+       downscale the results to fit the screen.
+
+       The compositor must reply to this request with a configure event
+       with the dimensions for the output on which the surface will
+       be made fullscreen.
+      </description>
+      <arg name="method" type="uint" enum="fullscreen_method" summary="method for resolving size conflict"/>
+      <arg name="framerate" type="uint" summary="framerate in mHz"/>
+      <arg name="output" type="object" interface="wl_output" allow-null="true"
+          summary="output on which the surface is to be fullscreen"/>
+    </request>
+
+    <request name="set_popup">
+      <description summary="make the surface a popup surface">
+       Map the surface as a popup.
+
+       A popup surface is a transient surface with an added pointer
+       grab.
+
+       An existing implicit grab will be changed to owner-events mode,
+       and the popup grab will continue after the implicit grab ends
+       (i.e. releasing the mouse button does not cause the popup to
+       be unmapped).
+
+       The popup grab continues until the window is destroyed or a
+       mouse button is pressed in any other client's window. A click
+       in any of the client's surfaces is reported as normal, however,
+       clicks in other clients' surfaces will be discarded and trigger
+       the callback.
+
+       The x and y arguments specify the location of the upper left
+       corner of the surface relative to the upper left corner of the
+       parent surface, in surface-local coordinates.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat" summary="seat whose pointer is used"/>
+      <arg name="serial" type="uint" summary="serial number of the implicit grab on the pointer"/>
+      <arg name="parent" type="object" interface="wl_surface" summary="parent surface"/>
+      <arg name="x" type="int" summary="surface-local x coordinate"/>
+      <arg name="y" type="int" summary="surface-local y coordinate"/>
+      <arg name="flags" type="uint" enum="transient" summary="transient surface behavior"/>
+    </request>
+
+    <request name="set_maximized">
+      <description summary="make the surface a maximized surface">
+       Map the surface as a maximized surface.
+
+       If an output parameter is given then the surface will be
+       maximized on that output. If the client does not specify the
+       output then the compositor will apply its policy - usually
+       choosing the output on which the surface has the biggest surface
+       area.
+
+       The compositor will reply with a configure event telling
+       the expected new surface size. The operation is completed
+       on the next buffer attach to this surface.
+
+       A maximized surface typically fills the entire output it is
+       bound to, except for desktop elements such as panels. This is
+       the main difference between a maximized shell surface and a
+       fullscreen shell surface.
+
+       The details depend on the compositor implementation.
+      </description>
+      <arg name="output" type="object" interface="wl_output" allow-null="true"
+          summary="output on which the surface is to be maximized"/>
+    </request>
+
+    <request name="set_title">
+      <description summary="set surface title">
+       Set a short title for the surface.
+
+       This string may be used to identify the surface in a task bar,
+       window list, or other user interface elements provided by the
+       compositor.
+
+       The string must be encoded in UTF-8.
+      </description>
+      <arg name="title" type="string" summary="surface title"/>
+    </request>
+
+    <request name="set_class">
+      <description summary="set surface class">
+       Set a class for the surface.
+
+       The surface class identifies the general class of applications
+       to which the surface belongs. A common convention is to use the
+       file name (or the full path if it is a non-standard location) of
+       the application's .desktop file as the class.
+      </description>
+      <arg name="class_" type="string" summary="surface class"/>
+    </request>
+
+    <event name="ping">
+      <description summary="ping client">
+       Ping a client to check if it is receiving events and sending
+       requests. A client is expected to reply with a pong request.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the ping"/>
+    </event>
+
+    <event name="configure">
+      <description summary="suggest resize">
+       The configure event asks the client to resize its surface.
+
+       The size is a hint, in the sense that the client is free to
+       ignore it if it doesn't resize, pick a smaller size (to
+       satisfy aspect ratio or resize in steps of NxM pixels).
+
+       The edges parameter provides a hint about how the surface
+       was resized. The client may use this information to decide
+       how to adjust its content to the new size (e.g. a scrolling
+       area might adjust its content position to leave the viewable
+       content unmoved).
+
+       The client is free to dismiss all but the last configure
+       event it received.
+
+       The width and height arguments specify the size of the window
+       in surface-local coordinates.
+      </description>
+      <arg name="edges" type="uint" enum="resize" summary="how the surface was resized"/>
+      <arg name="width" type="int" summary="new width of the surface"/>
+      <arg name="height" type="int" summary="new height of the surface"/>
+    </event>
+
+    <event name="popup_done">
+      <description summary="popup interaction is done">
+       The popup_done event is sent out when a popup grab is broken,
+       that is, when the user clicks a surface that doesn't belong
+       to the client owning the popup surface.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="wl_surface" version="4">
+    <description summary="an onscreen surface">
+      A surface is a rectangular area that is displayed on the screen.
+      It has a location, size and pixel contents.
+
+      The size of a surface (and relative positions on it) is described
+      in surface-local coordinates, which may differ from the buffer
+      coordinates of the pixel content, in case a buffer_transform
+      or a buffer_scale is used.
+
+      A surface without a "role" is fairly useless: a compositor does
+      not know where, when or how to present it. The role is the
+      purpose of a wl_surface. Examples of roles are a cursor for a
+      pointer (as set by wl_pointer.set_cursor), a drag icon
+      (wl_data_device.start_drag), a sub-surface
+      (wl_subcompositor.get_subsurface), and a window as defined by a
+      shell protocol (e.g. wl_shell.get_shell_surface).
+
+      A surface can have only one role at a time. Initially a
+      wl_surface does not have a role. Once a wl_surface is given a
+      role, it is set permanently for the whole lifetime of the
+      wl_surface object. Giving the current role again is allowed,
+      unless explicitly forbidden by the relevant interface
+      specification.
+
+      Surface roles are given by requests in other interfaces such as
+      wl_pointer.set_cursor. The request should explicitly mention
+      that this request gives a role to a wl_surface. Often, this
+      request also creates a new protocol object that represents the
+      role and adds additional functionality to wl_surface. When a
+      client wants to destroy a wl_surface, they must destroy this 'role
+      object' before the wl_surface.
+
+      Destroying the role object does not remove the role from the
+      wl_surface, but it may stop the wl_surface from "playing the role".
+      For instance, if a wl_subsurface object is destroyed, the wl_surface
+      it was created for will be unmapped and forget its position and
+      z-order. It is allowed to create a wl_subsurface for the same
+      wl_surface again, but it is not allowed to use the wl_surface as
+      a cursor (cursor is a different role than sub-surface, and role
+      switching is not allowed).
+    </description>
+
+    <enum name="error">
+      <description summary="wl_surface error values">
+       These errors can be emitted in response to wl_surface requests.
+      </description>
+      <entry name="invalid_scale" value="0" summary="buffer scale value is invalid"/>
+      <entry name="invalid_transform" value="1" summary="buffer transform value is invalid"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="delete surface">
+       Deletes the surface and invalidates its object ID.
+      </description>
+    </request>
+
+    <request name="attach">
+      <description summary="set the surface contents">
+       Set a buffer as the content of this surface.
+
+       The new size of the surface is calculated based on the buffer
+       size transformed by the inverse buffer_transform and the
+       inverse buffer_scale. This means that the supplied buffer
+       must be an integer multiple of the buffer_scale.
+
+       The x and y arguments specify the location of the new pending
+       buffer's upper left corner, relative to the current buffer's upper
+       left corner, in surface-local coordinates. In other words, the
+       x and y, combined with the new surface size define in which
+       directions the surface's size changes.
+
+       Surface contents are double-buffered state, see wl_surface.commit.
+
+       The initial surface contents are void; there is no content.
+       wl_surface.attach assigns the given wl_buffer as the pending
+       wl_buffer. wl_surface.commit makes the pending wl_buffer the new
+       surface contents, and the size of the surface becomes the size
+       calculated from the wl_buffer, as described above. After commit,
+       there is no pending buffer until the next attach.
+
+       Committing a pending wl_buffer allows the compositor to read the
+       pixels in the wl_buffer. The compositor may access the pixels at
+       any time after the wl_surface.commit request. When the compositor
+       will not access the pixels anymore, it will send the
+       wl_buffer.release event. Only after receiving wl_buffer.release,
+       the client may reuse the wl_buffer. A wl_buffer that has been
+       attached and then replaced by another attach instead of committed
+       will not receive a release event, and is not used by the
+       compositor.
+
+       Destroying the wl_buffer after wl_buffer.release does not change
+       the surface contents. However, if the client destroys the
+       wl_buffer before receiving the wl_buffer.release event, the surface
+       contents become undefined immediately.
+
+       If wl_surface.attach is sent with a NULL wl_buffer, the
+       following wl_surface.commit will remove the surface content.
+      </description>
+      <arg name="buffer" type="object" interface="wl_buffer" allow-null="true"
+          summary="buffer of surface contents"/>
+      <arg name="x" type="int" summary="surface-local x coordinate"/>
+      <arg name="y" type="int" summary="surface-local y coordinate"/>
+    </request>
+
+    <request name="damage">
+      <description summary="mark part of the surface damaged">
+       This request is used to describe the regions where the pending
+       buffer is different from the current surface contents, and where
+       the surface therefore needs to be repainted. The compositor
+       ignores the parts of the damage that fall outside of the surface.
+
+       Damage is double-buffered state, see wl_surface.commit.
+
+       The damage rectangle is specified in surface-local coordinates,
+       where x and y specify the upper left corner of the damage rectangle.
+
+       The initial value for pending damage is empty: no damage.
+       wl_surface.damage adds pending damage: the new pending damage
+       is the union of old pending damage and the given rectangle.
+
+       wl_surface.commit assigns pending damage as the current damage,
+       and clears pending damage. The server will clear the current
+       damage as it repaints the surface.
+
+       Alternatively, damage can be posted with wl_surface.damage_buffer
+       which uses buffer coordinates instead of surface coordinates,
+       and is probably the preferred and intuitive way of doing this.
+      </description>
+      <arg name="x" type="int" summary="surface-local x coordinate"/>
+      <arg name="y" type="int" summary="surface-local y coordinate"/>
+      <arg name="width" type="int" summary="width of damage rectangle"/>
+      <arg name="height" type="int" summary="height of damage rectangle"/>
+    </request>
+
+    <request name="frame">
+      <description summary="request a frame throttling hint">
+       Request a notification when it is a good time to start drawing a new
+       frame, by creating a frame callback. This is useful for throttling
+       redrawing operations, and driving animations.
+
+       When a client is animating on a wl_surface, it can use the 'frame'
+       request to get notified when it is a good time to draw and commit the
+       next frame of animation. If the client commits an update earlier than
+       that, it is likely that some updates will not make it to the display,
+       and the client is wasting resources by drawing too often.
+
+       The frame request will take effect on the next wl_surface.commit.
+       The notification will only be posted for one frame unless
+       requested again. For a wl_surface, the notifications are posted in
+       the order the frame requests were committed.
+
+       The server must send the notifications so that a client
+       will not send excessive updates, while still allowing
+       the highest possible update rate for clients that wait for the reply
+       before drawing again. The server should give some time for the client
+       to draw and commit after sending the frame callback events to let it
+       hit the next output refresh.
+
+       A server should avoid signaling the frame callbacks if the
+       surface is not visible in any way, e.g. the surface is off-screen,
+       or completely obscured by other opaque surfaces.
+
+       The object returned by this request will be destroyed by the
+       compositor after the callback is fired and as such the client must not
+       attempt to use it after that point.
+
+       The callback_data passed in the callback is the current time, in
+       milliseconds, with an undefined base.
+      </description>
+      <arg name="callback" type="new_id" interface="wl_callback" summary="callback object for the frame request"/>
+    </request>
+
+    <request name="set_opaque_region">
+      <description summary="set opaque region">
+       This request sets the region of the surface that contains
+       opaque content.
+
+       The opaque region is an optimization hint for the compositor
+       that lets it optimize the redrawing of content behind opaque
+       regions.  Setting an opaque region is not required for correct
+       behaviour, but marking transparent content as opaque will result
+       in repaint artifacts.
+
+       The opaque region is specified in surface-local coordinates.
+
+       The compositor ignores the parts of the opaque region that fall
+       outside of the surface.
+
+       Opaque region is double-buffered state, see wl_surface.commit.
+
+       wl_surface.set_opaque_region changes the pending opaque region.
+       wl_surface.commit copies the pending region to the current region.
+       Otherwise, the pending and current regions are never changed.
+
+       The initial value for an opaque region is empty. Setting the pending
+       opaque region has copy semantics, and the wl_region object can be
+       destroyed immediately. A NULL wl_region causes the pending opaque
+       region to be set to empty.
+      </description>
+      <arg name="region" type="object" interface="wl_region" allow-null="true"
+          summary="opaque region of the surface"/>
+    </request>
+
+    <request name="set_input_region">
+      <description summary="set input region">
+       This request sets the region of the surface that can receive
+       pointer and touch events.
+
+       Input events happening outside of this region will try the next
+       surface in the server surface stack. The compositor ignores the
+       parts of the input region that fall outside of the surface.
+
+       The input region is specified in surface-local coordinates.
+
+       Input region is double-buffered state, see wl_surface.commit.
+
+       wl_surface.set_input_region changes the pending input region.
+       wl_surface.commit copies the pending region to the current region.
+       Otherwise the pending and current regions are never changed,
+       except cursor and icon surfaces are special cases, see
+       wl_pointer.set_cursor and wl_data_device.start_drag.
+
+       The initial value for an input region is infinite. That means the
+       whole surface will accept input. Setting the pending input region
+       has copy semantics, and the wl_region object can be destroyed
+       immediately. A NULL wl_region causes the input region to be set
+       to infinite.
+      </description>
+      <arg name="region" type="object" interface="wl_region" allow-null="true"
+          summary="input region of the surface"/>
+    </request>
+
+    <request name="commit">
+      <description summary="commit pending surface state">
+       Surface state (input, opaque, and damage regions, attached buffers,
+       etc.) is double-buffered. Protocol requests modify the pending state,
+       as opposed to the current state in use by the compositor. A commit
+       request atomically applies all pending state, replacing the current
+       state. After commit, the new pending state is as documented for each
+       related request.
+
+       On commit, a pending wl_buffer is applied first, and all other state
+       second. This means that all coordinates in double-buffered state are
+       relative to the new wl_buffer coming into use, except for
+       wl_surface.attach itself. If there is no pending wl_buffer, the
+       coordinates are relative to the current surface contents.
+
+       All requests that need a commit to become effective are documented
+       to affect double-buffered state.
+
+       Other interfaces may add further double-buffered surface state.
+      </description>
+    </request>
+
+    <event name="enter">
+      <description summary="surface enters an output">
+       This is emitted whenever a surface's creation, movement, or resizing
+       results in some part of it being within the scanout region of an
+       output.
+
+       Note that a surface may be overlapping with zero or more outputs.
+      </description>
+      <arg name="output" type="object" interface="wl_output" summary="output entered by the surface"/>
+    </event>
+
+    <event name="leave">
+      <description summary="surface leaves an output">
+       This is emitted whenever a surface's creation, movement, or resizing
+       results in it no longer having any part of it within the scanout region
+       of an output.
+      </description>
+      <arg name="output" type="object" interface="wl_output" summary="output left by the surface"/>
+    </event>
+
+    <!-- Version 2 additions -->
+
+    <request name="set_buffer_transform" since="2">
+      <description summary="sets the buffer transformation">
+       This request sets an optional transformation on how the compositor
+       interprets the contents of the buffer attached to the surface. The
+       accepted values for the transform parameter are the values for
+       wl_output.transform.
+
+       Buffer transform is double-buffered state, see wl_surface.commit.
+
+       A newly created surface has its buffer transformation set to normal.
+
+       wl_surface.set_buffer_transform changes the pending buffer
+       transformation. wl_surface.commit copies the pending buffer
+       transformation to the current one. Otherwise, the pending and current
+       values are never changed.
+
+       The purpose of this request is to allow clients to render content
+       according to the output transform, thus permitting the compositor to
+       use certain optimizations even if the display is rotated. Using
+       hardware overlays and scanning out a client buffer for fullscreen
+       surfaces are examples of such optimizations. Those optimizations are
+       highly dependent on the compositor implementation, so the use of this
+       request should be considered on a case-by-case basis.
+
+       Note that if the transform value includes 90 or 270 degree rotation,
+       the width of the buffer will become the surface height and the height
+       of the buffer will become the surface width.
+
+       If transform is not one of the values from the
+       wl_output.transform enum the invalid_transform protocol error
+       is raised.
+      </description>
+      <arg name="transform" type="int" enum="wl_output.transform"
+          summary="transform for interpreting buffer contents"/>
+    </request>
+
+    <!-- Version 3 additions -->
+
+    <request name="set_buffer_scale" since="3">
+      <description summary="sets the buffer scaling factor">
+       This request sets an optional scaling factor on how the compositor
+       interprets the contents of the buffer attached to the window.
+
+       Buffer scale is double-buffered state, see wl_surface.commit.
+
+       A newly created surface has its buffer scale set to 1.
+
+       wl_surface.set_buffer_scale changes the pending buffer scale.
+       wl_surface.commit copies the pending buffer scale to the current one.
+       Otherwise, the pending and current values are never changed.
+
+       The purpose of this request is to allow clients to supply higher
+       resolution buffer data for use on high resolution outputs. It is
+       intended that you pick the same buffer scale as the scale of the
+       output that the surface is displayed on. This means the compositor
+       can avoid scaling when rendering the surface on that output.
+
+       Note that if the scale is larger than 1, then you have to attach
+       a buffer that is larger (by a factor of scale in each dimension)
+       than the desired surface size.
+
+       If scale is not positive the invalid_scale protocol error is
+       raised.
+      </description>
+      <arg name="scale" type="int"
+          summary="positive scale for interpreting buffer contents"/>
+    </request>
+
+    <!-- Version 4 additions -->
+    <request name="damage_buffer" since="4">
+      <description summary="mark part of the surface damaged using buffer coordinates">
+       This request is used to describe the regions where the pending
+       buffer is different from the current surface contents, and where
+       the surface therefore needs to be repainted. The compositor
+       ignores the parts of the damage that fall outside of the surface.
+
+       Damage is double-buffered state, see wl_surface.commit.
+
+       The damage rectangle is specified in buffer coordinates,
+       where x and y specify the upper left corner of the damage rectangle.
+
+       The initial value for pending damage is empty: no damage.
+       wl_surface.damage_buffer adds pending damage: the new pending
+       damage is the union of old pending damage and the given rectangle.
+
+       wl_surface.commit assigns pending damage as the current damage,
+       and clears pending damage. The server will clear the current
+       damage as it repaints the surface.
+
+       This request differs from wl_surface.damage in only one way - it
+       takes damage in buffer coordinates instead of surface-local
+       coordinates. While this generally is more intuitive than surface
+       coordinates, it is especially desirable when using wp_viewport
+       or when a drawing library (like EGL) is unaware of buffer scale
+       and buffer transform.
+
+       Note: Because buffer transformation changes and damage requests may
+       be interleaved in the protocol stream, it is impossible to determine
+       the actual mapping between surface and buffer damage until
+       wl_surface.commit time. Therefore, compositors wishing to take both
+       kinds of damage into account will have to accumulate damage from the
+       two requests separately and only transform from one to the other
+       after receiving the wl_surface.commit.
+      </description>
+      <arg name="x" type="int" summary="buffer-local x coordinate"/>
+      <arg name="y" type="int" summary="buffer-local y coordinate"/>
+      <arg name="width" type="int" summary="width of damage rectangle"/>
+      <arg name="height" type="int" summary="height of damage rectangle"/>
+    </request>
+   </interface>
+
+  <interface name="wl_seat" version="6">
+    <description summary="group of input devices">
+      A seat is a group of keyboards, pointer and touch devices. This
+      object is published as a global during start up, or when such a
+      device is hot plugged.  A seat typically has a pointer and
+      maintains a keyboard focus and a pointer focus.
+    </description>
+
+    <enum name="capability" bitfield="true">
+      <description summary="seat capability bitmask">
+       This is a bitmask of capabilities this seat has; if a member is
+       set, then it is present on the seat.
+      </description>
+      <entry name="pointer" value="1" summary="the seat has pointer devices"/>
+      <entry name="keyboard" value="2" summary="the seat has one or more keyboards"/>
+      <entry name="touch" value="4" summary="the seat has touch devices"/>
+    </enum>
+
+    <event name="capabilities">
+      <description summary="seat capabilities changed">
+       This is emitted whenever a seat gains or loses the pointer,
+       keyboard or touch capabilities.  The argument is a capability
+       enum containing the complete set of capabilities this seat has.
+
+       When the pointer capability is added, a client may create a
+       wl_pointer object using the wl_seat.get_pointer request. This object
+       will receive pointer events until the capability is removed in the
+       future.
+
+       When the pointer capability is removed, a client should destroy the
+       wl_pointer objects associated with the seat where the capability was
+       removed, using the wl_pointer.release request. No further pointer
+       events will be received on these objects.
+
+       In some compositors, if a seat regains the pointer capability and a
+       client has a previously obtained wl_pointer object of version 4 or
+       less, that object may start sending pointer events again. This
+       behavior is considered a misinterpretation of the intended behavior
+       and must not be relied upon by the client. wl_pointer objects of
+       version 5 or later must not send events if created before the most
+       recent event notifying the client of an added pointer capability.
+
+       The above behavior also applies to wl_keyboard and wl_touch with the
+       keyboard and touch capabilities, respectively.
+      </description>
+      <arg name="capabilities" type="uint" enum="capability" summary="capabilities of the seat"/>
+    </event>
+
+    <request name="get_pointer">
+      <description summary="return pointer object">
+       The ID provided will be initialized to the wl_pointer interface
+       for this seat.
+
+       This request only takes effect if the seat has the pointer
+       capability, or has had the pointer capability in the past.
+       It is a protocol violation to issue this request on a seat that has
+       never had the pointer capability.
+      </description>
+      <arg name="id" type="new_id" interface="wl_pointer" summary="seat pointer"/>
+    </request>
+
+    <request name="get_keyboard">
+      <description summary="return keyboard object">
+       The ID provided will be initialized to the wl_keyboard interface
+       for this seat.
+
+       This request only takes effect if the seat has the keyboard
+       capability, or has had the keyboard capability in the past.
+       It is a protocol violation to issue this request on a seat that has
+       never had the keyboard capability.
+      </description>
+      <arg name="id" type="new_id" interface="wl_keyboard" summary="seat keyboard"/>
+    </request>
+
+    <request name="get_touch">
+      <description summary="return touch object">
+       The ID provided will be initialized to the wl_touch interface
+       for this seat.
+
+       This request only takes effect if the seat has the touch
+       capability, or has had the touch capability in the past.
+       It is a protocol violation to issue this request on a seat that has
+       never had the touch capability.
+      </description>
+      <arg name="id" type="new_id" interface="wl_touch" summary="seat touch interface"/>
+    </request>
+
+    <!-- Version 2 additions -->
+
+    <event name="name" since="2">
+      <description summary="unique identifier for this seat">
+       In a multiseat configuration this can be used by the client to help
+       identify which physical devices the seat represents. Based on
+       the seat configuration used by the compositor.
+      </description>
+      <arg name="name" type="string" summary="seat identifier"/>
+    </event>
+
+    <!-- Version 5 additions -->
+
+    <request name="release" type="destructor" since="5">
+      <description summary="release the seat object">
+       Using this request a client can tell the server that it is not going to
+       use the seat object anymore.
+      </description>
+    </request>
+
+  </interface>
+
+  <interface name="wl_pointer" version="6">
+    <description summary="pointer input device">
+      The wl_pointer interface represents one or more input devices,
+      such as mice, which control the pointer location and pointer_focus
+      of a seat.
+
+      The wl_pointer interface generates motion, enter and leave
+      events for the surfaces that the pointer is located over,
+      and button and axis events for button presses, button releases
+      and scrolling.
+    </description>
+
+    <enum name="error">
+      <entry name="role" value="0" summary="given wl_surface has another role"/>
+    </enum>
+
+    <request name="set_cursor">
+      <description summary="set the pointer surface">
+       Set the pointer surface, i.e., the surface that contains the
+       pointer image (cursor). This request gives the surface the role
+       of a cursor. If the surface already has another role, it raises
+       a protocol error.
+
+       The cursor actually changes only if the pointer
+       focus for this device is one of the requesting client's surfaces
+       or the surface parameter is the current pointer surface. If
+       there was a previous surface set with this request it is
+       replaced. If surface is NULL, the pointer image is hidden.
+
+       The parameters hotspot_x and hotspot_y define the position of
+       the pointer surface relative to the pointer location. Its
+       top-left corner is always at (x, y) - (hotspot_x, hotspot_y),
+       where (x, y) are the coordinates of the pointer location, in
+       surface-local coordinates.
+
+       On surface.attach requests to the pointer surface, hotspot_x
+       and hotspot_y are decremented by the x and y parameters
+       passed to the request. Attach must be confirmed by
+       wl_surface.commit as usual.
+
+       The hotspot can also be updated by passing the currently set
+       pointer surface to this request with new values for hotspot_x
+       and hotspot_y.
+
+       The current and pending input regions of the wl_surface are
+       cleared, and wl_surface.set_input_region is ignored until the
+       wl_surface is no longer used as the cursor. When the use as a
+       cursor ends, the current and pending input regions become
+       undefined, and the wl_surface is unmapped.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the enter event"/>
+      <arg name="surface" type="object" interface="wl_surface" allow-null="true"
+          summary="pointer surface"/>
+      <arg name="hotspot_x" type="int" summary="surface-local x coordinate"/>
+      <arg name="hotspot_y" type="int" summary="surface-local y coordinate"/>
+    </request>
+
+    <event name="enter">
+      <description summary="enter event">
+       Notification that this seat's pointer is focused on a certain
+       surface.
+
+       When a seat's focus enters a surface, the pointer image
+       is undefined and a client should respond to this event by setting
+       an appropriate pointer image with the set_cursor request.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the enter event"/>
+      <arg name="surface" type="object" interface="wl_surface" summary="surface entered by the pointer"/>
+      <arg name="surface_x" type="fixed" summary="surface-local x coordinate"/>
+      <arg name="surface_y" type="fixed" summary="surface-local y coordinate"/>
+    </event>
+
+    <event name="leave">
+      <description summary="leave event">
+       Notification that this seat's pointer is no longer focused on
+       a certain surface.
+
+       The leave notification is sent before the enter notification
+       for the new focus.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the leave event"/>
+      <arg name="surface" type="object" interface="wl_surface" summary="surface left by the pointer"/>
+    </event>
+
+    <event name="motion">
+      <description summary="pointer motion event">
+       Notification of pointer location change. The arguments
+       surface_x and surface_y are the location relative to the
+       focused surface.
+      </description>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="surface_x" type="fixed" summary="surface-local x coordinate"/>
+      <arg name="surface_y" type="fixed" summary="surface-local y coordinate"/>
+    </event>
+
+    <enum name="button_state">
+      <description summary="physical button state">
+       Describes the physical state of a button that produced the button
+       event.
+      </description>
+      <entry name="released" value="0" summary="the button is not pressed"/>
+      <entry name="pressed" value="1" summary="the button is pressed"/>
+    </enum>
+
+    <event name="button">
+      <description summary="pointer button event">
+       Mouse button click and release notifications.
+
+       The location of the click is given by the last motion or
+       enter event.
+       The time argument is a timestamp with millisecond
+       granularity, with an undefined base.
+
+       The button is a button code as defined in the Linux kernel's
+       linux/input-event-codes.h header file, e.g. BTN_LEFT.
+
+       Any 16-bit button code value is reserved for future additions to the
+       kernel's event code list. All other button codes above 0xFFFF are
+       currently undefined but may be used in future versions of this
+       protocol.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the button event"/>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="button" type="uint" summary="button that produced the event"/>
+      <arg name="state" type="uint" enum="button_state" summary="physical state of the button"/>
+    </event>
+
+    <enum name="axis">
+      <description summary="axis types">
+       Describes the axis types of scroll events.
+      </description>
+      <entry name="vertical_scroll" value="0" summary="vertical axis"/>
+      <entry name="horizontal_scroll" value="1" summary="horizontal axis"/>
+    </enum>
+
+    <event name="axis">
+      <description summary="axis event">
+       Scroll and other axis notifications.
+
+       For scroll events (vertical and horizontal scroll axes), the
+       value parameter is the length of a vector along the specified
+       axis in a coordinate space identical to those of motion events,
+       representing a relative movement along the specified axis.
+
+       For devices that support movements non-parallel to axes multiple
+       axis events will be emitted.
+
+       When applicable, for example for touch pads, the server can
+       choose to emit scroll events where the motion vector is
+       equivalent to a motion event vector.
+
+       When applicable, a client can transform its content relative to the
+       scroll distance.
+      </description>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="axis" type="uint" enum="axis" summary="axis type"/>
+      <arg name="value" type="fixed" summary="length of vector in surface-local coordinate space"/>
+    </event>
+
+    <!-- Version 3 additions -->
+
+    <request name="release" type="destructor" since="3">
+      <description summary="release the pointer object">
+       Using this request a client can tell the server that it is not going to
+       use the pointer object anymore.
+
+       This request destroys the pointer proxy object, so clients must not call
+       wl_pointer_destroy() after using this request.
+      </description>
+    </request>
+
+    <!-- Version 5 additions -->
+
+    <event name="frame" since="5">
+      <description summary="end of a pointer event sequence">
+       Indicates the end of a set of events that logically belong together.
+       A client is expected to accumulate the data in all events within the
+       frame before proceeding.
+
+       All wl_pointer events before a wl_pointer.frame event belong
+       logically together. For example, in a diagonal scroll motion the
+       compositor will send an optional wl_pointer.axis_source event, two
+       wl_pointer.axis events (horizontal and vertical) and finally a
+       wl_pointer.frame event. The client may use this information to
+       calculate a diagonal vector for scrolling.
+
+       When multiple wl_pointer.axis events occur within the same frame,
+       the motion vector is the combined motion of all events.
+       When a wl_pointer.axis and a wl_pointer.axis_stop event occur within
+       the same frame, this indicates that axis movement in one axis has
+       stopped but continues in the other axis.
+       When multiple wl_pointer.axis_stop events occur within the same
+       frame, this indicates that these axes stopped in the same instance.
+
+       A wl_pointer.frame event is sent for every logical event group,
+       even if the group only contains a single wl_pointer event.
+       Specifically, a client may get a sequence: motion, frame, button,
+       frame, axis, frame, axis_stop, frame.
+
+       The wl_pointer.enter and wl_pointer.leave events are logical events
+       generated by the compositor and not the hardware. These events are
+       also grouped by a wl_pointer.frame. When a pointer moves from one
+       surface to another, a compositor should group the
+       wl_pointer.leave event within the same wl_pointer.frame.
+       However, a client must not rely on wl_pointer.leave and
+       wl_pointer.enter being in the same wl_pointer.frame.
+       Compositor-specific policies may require the wl_pointer.leave and
+       wl_pointer.enter event being split across multiple wl_pointer.frame
+       groups.
+      </description>
+    </event>
+
+    <enum name="axis_source">
+      <description summary="axis source types">
+       Describes the source types for axis events. This indicates to the
+       client how an axis event was physically generated; a client may
+       adjust the user interface accordingly. For example, scroll events
+       from a "finger" source may be in a smooth coordinate space with
+       kinetic scrolling whereas a "wheel" source may be in discrete steps
+       of a number of lines.
+
+       The "continuous" axis source is a device generating events in a
+       continuous coordinate space, but using something other than a
+       finger. One example for this source is button-based scrolling where
+       the vertical motion of a device is converted to scroll events while
+       a button is held down.
+
+       The "wheel tilt" axis source indicates that the actual device is a
+       wheel but the scroll event is not caused by a rotation but a
+       (usually sideways) tilt of the wheel.
+      </description>
+      <entry name="wheel" value="0" summary="a physical wheel rotation" />
+      <entry name="finger" value="1" summary="finger on a touch surface" />
+      <entry name="continuous" value="2" summary="continuous coordinate space"/>
+      <entry name="wheel_tilt" value="3" summary="a physical wheel tilt" since="6"/>
+    </enum>
+
+    <event name="axis_source" since="5">
+      <description summary="axis source event">
+       Source information for scroll and other axes.
+
+       This event does not occur on its own. It is sent before a
+       wl_pointer.frame event and carries the source information for
+       all events within that frame.
+
+       The source specifies how this event was generated. If the source is
+       wl_pointer.axis_source.finger, a wl_pointer.axis_stop event will be
+       sent when the user lifts the finger off the device.
+
+       If the source is wl_pointer.axis_source.wheel,
+       wl_pointer.axis_source.wheel_tilt or
+       wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event may
+       or may not be sent. Whether a compositor sends an axis_stop event
+       for these sources is hardware-specific and implementation-dependent;
+       clients must not rely on receiving an axis_stop event for these
+       scroll sources and should treat scroll sequences from these scroll
+       sources as unterminated by default.
+
+       This event is optional. If the source is unknown for a particular
+       axis event sequence, no event is sent.
+       Only one wl_pointer.axis_source event is permitted per frame.
+
+       The order of wl_pointer.axis_discrete and wl_pointer.axis_source is
+       not guaranteed.
+      </description>
+      <arg name="axis_source" type="uint" enum="axis_source" summary="source of the axis event"/>
+    </event>
+
+    <event name="axis_stop" since="5">
+      <description summary="axis stop event">
+       Stop notification for scroll and other axes.
+
+       For some wl_pointer.axis_source types, a wl_pointer.axis_stop event
+       is sent to notify a client that the axis sequence has terminated.
+       This enables the client to implement kinetic scrolling.
+       See the wl_pointer.axis_source documentation for information on when
+       this event may be generated.
+
+       Any wl_pointer.axis events with the same axis_source after this
+       event should be considered as the start of a new axis motion.
+
+       The timestamp is to be interpreted identical to the timestamp in the
+       wl_pointer.axis event. The timestamp value may be the same as a
+       preceding wl_pointer.axis event.
+      </description>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="axis" type="uint" enum="axis" summary="the axis stopped with this event"/>
+    </event>
+
+    <event name="axis_discrete" since="5">
+      <description summary="axis click event">
+       Discrete step information for scroll and other axes.
+
+       This event carries the axis value of the wl_pointer.axis event in
+       discrete steps (e.g. mouse wheel clicks).
+
+       This event does not occur on its own, it is coupled with a
+       wl_pointer.axis event that represents this axis value on a
+       continuous scale. The protocol guarantees that each axis_discrete
+       event is always followed by exactly one axis event with the same
+       axis number within the same wl_pointer.frame. Note that the protocol
+       allows for other events to occur between the axis_discrete and
+       its coupled axis event, including other axis_discrete or axis
+       events.
+
+       This event is optional; continuous scrolling devices
+       like two-finger scrolling on touchpads do not have discrete
+       steps and do not generate this event.
+
+       The discrete value carries the directional information. e.g. a value
+       of -2 is two steps towards the negative direction of this axis.
+
+       The axis number is identical to the axis number in the associated
+       axis event.
+
+       The order of wl_pointer.axis_discrete and wl_pointer.axis_source is
+       not guaranteed.
+      </description>
+      <arg name="axis" type="uint" enum="axis" summary="axis type"/>
+      <arg name="discrete" type="int" summary="number of steps"/>
+    </event>
+  </interface>
+
+  <interface name="wl_keyboard" version="6">
+    <description summary="keyboard input device">
+      The wl_keyboard interface represents one or more keyboards
+      associated with a seat.
+    </description>
+
+    <enum name="keymap_format">
+      <description summary="keyboard mapping format">
+       This specifies the format of the keymap provided to the
+       client with the wl_keyboard.keymap event.
+      </description>
+      <entry name="no_keymap" value="0"
+            summary="no keymap; client must understand how to interpret the raw keycode"/>
+      <entry name="xkb_v1" value="1"
+            summary="libxkbcommon compatible; to determine the xkb keycode, clients must add 8 to the key event keycode"/>
+    </enum>
+
+    <event name="keymap">
+      <description summary="keyboard mapping">
+       This event provides a file descriptor to the client which can be
+       memory-mapped to provide a keyboard mapping description.
+      </description>
+      <arg name="format" type="uint" enum="keymap_format" summary="keymap format"/>
+      <arg name="fd" type="fd" summary="keymap file descriptor"/>
+      <arg name="size" type="uint" summary="keymap size, in bytes"/>
+    </event>
+
+    <event name="enter">
+      <description summary="enter event">
+       Notification that this seat's keyboard focus is on a certain
+       surface.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the enter event"/>
+      <arg name="surface" type="object" interface="wl_surface" summary="surface gaining keyboard focus"/>
+      <arg name="keys" type="array" summary="the currently pressed keys"/>
+    </event>
+
+    <event name="leave">
+      <description summary="leave event">
+       Notification that this seat's keyboard focus is no longer on
+       a certain surface.
+
+       The leave notification is sent before the enter notification
+       for the new focus.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the leave event"/>
+      <arg name="surface" type="object" interface="wl_surface" summary="surface that lost keyboard focus"/>
+    </event>
+
+    <enum name="key_state">
+      <description summary="physical key state">
+       Describes the physical state of a key that produced the key event.
+      </description>
+      <entry name="released" value="0" summary="key is not pressed"/>
+      <entry name="pressed" value="1" summary="key is pressed"/>
+    </enum>
+
+    <event name="key">
+      <description summary="key event">
+       A key was pressed or released.
+       The time argument is a timestamp with millisecond
+       granularity, with an undefined base.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the key event"/>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="key" type="uint" summary="key that produced the event"/>
+      <arg name="state" type="uint" enum="key_state" summary="physical state of the key"/>
+    </event>
+
+    <event name="modifiers">
+      <description summary="modifier and group state">
+       Notifies clients that the modifier and/or group state has
+       changed, and it should update its local state.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the modifiers event"/>
+      <arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
+      <arg name="mods_latched" type="uint" summary="latched modifiers"/>
+      <arg name="mods_locked" type="uint" summary="locked modifiers"/>
+      <arg name="group" type="uint" summary="keyboard layout"/>
+    </event>
+
+    <!-- Version 3 additions -->
+
+    <request name="release" type="destructor" since="3">
+      <description summary="release the keyboard object"/>
+    </request>
+
+    <!-- Version 4 additions -->
+
+    <event name="repeat_info" since="4">
+      <description summary="repeat rate and delay">
+       Informs the client about the keyboard's repeat rate and delay.
+
+       This event is sent as soon as the wl_keyboard object has been created,
+       and is guaranteed to be received by the client before any key press
+       event.
+
+       Negative values for either rate or delay are illegal. A rate of zero
+       will disable any repeating (regardless of the value of delay).
+
+       This event can be sent later on as well with a new value if necessary,
+       so clients should continue listening for the event past the creation
+       of wl_keyboard.
+      </description>
+      <arg name="rate" type="int"
+          summary="the rate of repeating keys in characters per second"/>
+      <arg name="delay" type="int"
+          summary="delay in milliseconds since key down until repeating starts"/>
+    </event>
+  </interface>
+
+  <interface name="wl_touch" version="6">
+    <description summary="touchscreen input device">
+      The wl_touch interface represents a touchscreen
+      associated with a seat.
+
+      Touch interactions can consist of one or more contacts.
+      For each contact, a series of events is generated, starting
+      with a down event, followed by zero or more motion events,
+      and ending with an up event. Events relating to the same
+      contact point can be identified by the ID of the sequence.
+    </description>
+
+    <event name="down">
+      <description summary="touch down event and beginning of a touch sequence">
+       A new touch point has appeared on the surface. This touch point is
+       assigned a unique ID. Future events from this touch point reference
+       this ID. The ID ceases to be valid after a touch up event and may be
+       reused in the future.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the touch down event"/>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="surface" type="object" interface="wl_surface" summary="surface touched"/>
+      <arg name="id" type="int" summary="the unique ID of this touch point"/>
+      <arg name="x" type="fixed" summary="surface-local x coordinate"/>
+      <arg name="y" type="fixed" summary="surface-local y coordinate"/>
+    </event>
+
+    <event name="up">
+      <description summary="end of a touch event sequence">
+       The touch point has disappeared. No further events will be sent for
+       this touch point and the touch point's ID is released and may be
+       reused in a future touch down event.
+      </description>
+      <arg name="serial" type="uint" summary="serial number of the touch up event"/>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="id" type="int" summary="the unique ID of this touch point"/>
+    </event>
+
+    <event name="motion">
+      <description summary="update of touch point coordinates">
+       A touch point has changed coordinates.
+      </description>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="id" type="int" summary="the unique ID of this touch point"/>
+      <arg name="x" type="fixed" summary="surface-local x coordinate"/>
+      <arg name="y" type="fixed" summary="surface-local y coordinate"/>
+    </event>
+
+    <event name="frame">
+      <description summary="end of touch frame event">
+       Indicates the end of a set of events that logically belong together.
+       A client is expected to accumulate the data in all events within the
+       frame before proceeding.
+
+       A wl_touch.frame terminates at least one event but otherwise no
+       guarantee is provided about the set of events within a frame. A client
+       must assume that any state not updated in a frame is unchanged from the
+       previously known state.
+      </description>
+    </event>
+
+    <event name="cancel">
+      <description summary="touch session cancelled">
+       Sent if the compositor decides the touch stream is a global
+       gesture. No further events are sent to the clients from that
+       particular gesture. Touch cancellation applies to all touch points
+       currently active on this client's surface. The client is
+       responsible for finalizing the touch points, future touch points on
+       this surface may reuse the touch point ID.
+      </description>
+    </event>
+
+    <!-- Version 3 additions -->
+
+    <request name="release" type="destructor" since="3">
+      <description summary="release the touch object"/>
+    </request>
+
+    <!-- Version 6 additions -->
+
+    <event name="shape" since="6">
+      <description summary="update shape of touch point">
+       Sent when a touchpoint has changed its shape.
+
+       This event does not occur on its own. It is sent before a
+       wl_touch.frame event and carries the new shape information for
+       any previously reported, or new touch points of that frame.
+
+       Other events describing the touch point such as wl_touch.down,
+       wl_touch.motion or wl_touch.orientation may be sent within the
+       same wl_touch.frame. A client should treat these events as a single
+       logical touch point update. The order of wl_touch.shape,
+       wl_touch.orientation and wl_touch.motion is not guaranteed.
+       A wl_touch.down event is guaranteed to occur before the first
+       wl_touch.shape event for this touch ID but both events may occur within
+       the same wl_touch.frame.
+
+       A touchpoint shape is approximated by an ellipse through the major and
+       minor axis length. The major axis length describes the longer diameter
+       of the ellipse, while the minor axis length describes the shorter
+       diameter. Major and minor are orthogonal and both are specified in
+       surface-local coordinates. The center of the ellipse is always at the
+       touchpoint location as reported by wl_touch.down or wl_touch.move.
+
+       This event is only sent by the compositor if the touch device supports
+       shape reports. The client has to make reasonable assumptions about the
+       shape if it did not receive this event.
+      </description>
+      <arg name="id" type="int" summary="the unique ID of this touch point"/>
+      <arg name="major" type="fixed" summary="length of the major axis in surface-local coordinates"/>
+      <arg name="minor" type="fixed" summary="length of the minor axis in surface-local coordinates"/>
+    </event>
+
+    <event name="orientation" since="6">
+      <description summary="update orientation of touch point">
+       Sent when a touchpoint has changed its orientation.
+
+       This event does not occur on its own. It is sent before a
+       wl_touch.frame event and carries the new shape information for
+       any previously reported, or new touch points of that frame.
+
+       Other events describing the touch point such as wl_touch.down,
+       wl_touch.motion or wl_touch.shape may be sent within the
+       same wl_touch.frame. A client should treat these events as a single
+       logical touch point update. The order of wl_touch.shape,
+       wl_touch.orientation and wl_touch.motion is not guaranteed.
+       A wl_touch.down event is guaranteed to occur before the first
+       wl_touch.orientation event for this touch ID but both events may occur
+       within the same wl_touch.frame.
+
+       The orientation describes the clockwise angle of a touchpoint's major
+       axis to the positive surface y-axis and is normalized to the -180 to
+       +180 degree range. The granularity of orientation depends on the touch
+       device, some devices only support binary rotation values between 0 and
+       90 degrees.
+
+       This event is only sent by the compositor if the touch device supports
+       orientation reports.
+      </description>
+      <arg name="id" type="int" summary="the unique ID of this touch point"/>
+      <arg name="orientation" type="fixed" summary="angle between major axis and positive surface y-axis in degrees"/>
+    </event>
+  </interface>
+
+  <interface name="wl_output" version="3">
+    <description summary="compositor output region">
+      An output describes part of the compositor geometry.  The
+      compositor works in the 'compositor coordinate system' and an
+      output corresponds to a rectangular area in that space that is
+      actually visible.  This typically corresponds to a monitor that
+      displays part of the compositor space.  This object is published
+      as global during start up, or when a monitor is hotplugged.
+    </description>
+
+    <enum name="subpixel">
+      <description summary="subpixel geometry information">
+       This enumeration describes how the physical
+       pixels on an output are laid out.
+      </description>
+      <entry name="unknown" value="0" summary="unknown geometry"/>
+      <entry name="none" value="1" summary="no geometry"/>
+      <entry name="horizontal_rgb" value="2" summary="horizontal RGB"/>
+      <entry name="horizontal_bgr" value="3" summary="horizontal BGR"/>
+      <entry name="vertical_rgb" value="4" summary="vertical RGB"/>
+      <entry name="vertical_bgr" value="5" summary="vertical BGR"/>
+    </enum>
+
+    <enum name="transform">
+      <description summary="transform from framebuffer to output">
+       This describes the transform that a compositor will apply to a
+       surface to compensate for the rotation or mirroring of an
+       output device.
+
+       The flipped values correspond to an initial flip around a
+       vertical axis followed by rotation.
+
+       The purpose is mainly to allow clients to render accordingly and
+       tell the compositor, so that for fullscreen surfaces, the
+       compositor will still be able to scan out directly from client
+       surfaces.
+      </description>
+      <entry name="normal" value="0" summary="no transform"/>
+      <entry name="90" value="1" summary="90 degrees counter-clockwise"/>
+      <entry name="180" value="2" summary="180 degrees counter-clockwise"/>
+      <entry name="270" value="3" summary="270 degrees counter-clockwise"/>
+      <entry name="flipped" value="4" summary="180 degree flip around a vertical axis"/>
+      <entry name="flipped_90" value="5" summary="flip and rotate 90 degrees counter-clockwise"/>
+      <entry name="flipped_180" value="6" summary="flip and rotate 180 degrees counter-clockwise"/>
+      <entry name="flipped_270" value="7" summary="flip and rotate 270 degrees counter-clockwise"/>
+    </enum>
+
+    <event name="geometry">
+      <description summary="properties of the output">
+       The geometry event describes geometric properties of the output.
+       The event is sent when binding to the output object and whenever
+       any of the properties change.
+      </description>
+      <arg name="x" type="int"
+          summary="x position within the global compositor space"/>
+      <arg name="y" type="int"
+          summary="y position within the global compositor space"/>
+      <arg name="physical_width" type="int"
+          summary="width in millimeters of the output"/>
+      <arg name="physical_height" type="int"
+          summary="height in millimeters of the output"/>
+      <arg name="subpixel" type="int" enum="subpixel"
+          summary="subpixel orientation of the output"/>
+      <arg name="make" type="string"
+          summary="textual description of the manufacturer"/>
+      <arg name="model" type="string"
+          summary="textual description of the model"/>
+      <arg name="transform" type="int" enum="transform"
+          summary="transform that maps framebuffer to output"/>
+    </event>
+
+    <enum name="mode" bitfield="true">
+      <description summary="mode information">
+       These flags describe properties of an output mode.
+       They are used in the flags bitfield of the mode event.
+      </description>
+      <entry name="current" value="0x1"
+            summary="indicates this is the current mode"/>
+      <entry name="preferred" value="0x2"
+            summary="indicates this is the preferred mode"/>
+    </enum>
+
+    <event name="mode">
+      <description summary="advertise available modes for the output">
+       The mode event describes an available mode for the output.
+
+       The event is sent when binding to the output object and there
+       will always be one mode, the current mode.  The event is sent
+       again if an output changes mode, for the mode that is now
+       current.  In other words, the current mode is always the last
+       mode that was received with the current flag set.
+
+       The size of a mode is given in physical hardware units of
+       the output device. This is not necessarily the same as
+       the output size in the global compositor space. For instance,
+       the output may be scaled, as described in wl_output.scale,
+       or transformed, as described in wl_output.transform.
+      </description>
+      <arg name="flags" type="uint" enum="mode" summary="bitfield of mode flags"/>
+      <arg name="width" type="int" summary="width of the mode in hardware units"/>
+      <arg name="height" type="int" summary="height of the mode in hardware units"/>
+      <arg name="refresh" type="int" summary="vertical refresh rate in mHz"/>
+    </event>
+
+    <!-- Version 2 additions -->
+
+    <event name="done" since="2">
+      <description summary="sent all information about output">
+       This event is sent after all other properties have been
+       sent after binding to the output object and after any
+       other property changes done after that. This allows
+       changes to the output properties to be seen as
+       atomic, even if they happen via multiple events.
+      </description>
+    </event>
+
+    <event name="scale" since="2">
+      <description summary="output scaling properties">
+       This event contains scaling geometry information
+       that is not in the geometry event. It may be sent after
+       binding the output object or if the output scale changes
+       later. If it is not sent, the client should assume a
+       scale of 1.
+
+       A scale larger than 1 means that the compositor will
+       automatically scale surface buffers by this amount
+       when rendering. This is used for very high resolution
+       displays where applications rendering at the native
+       resolution would be too small to be legible.
+
+       It is intended that scaling aware clients track the
+       current output of a surface, and if it is on a scaled
+       output it should use wl_surface.set_buffer_scale with
+       the scale of the output. That way the compositor can
+       avoid scaling the surface, and the client can supply
+       a higher detail image.
+      </description>
+      <arg name="factor" type="int" summary="scaling factor of output"/>
+    </event>
+
+    <!-- Version 3 additions -->
+
+    <request name="release" type="destructor" since="3">
+      <description summary="release the output object">
+       Using this request a client can tell the server that it is not going to
+       use the output object anymore.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="wl_region" version="1">
+    <description summary="region interface">
+      A region object describes an area.
+
+      Region objects are used to describe the opaque and input
+      regions of a surface.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy region">
+       Destroy the region.  This will invalidate the object ID.
+      </description>
+    </request>
+
+    <request name="add">
+      <description summary="add rectangle to region">
+       Add the specified rectangle to the region.
+      </description>
+      <arg name="x" type="int" summary="region-local x coordinate"/>
+      <arg name="y" type="int" summary="region-local y coordinate"/>
+      <arg name="width" type="int" summary="rectangle width"/>
+      <arg name="height" type="int" summary="rectangle height"/>
+    </request>
+
+    <request name="subtract">
+      <description summary="subtract rectangle from region">
+       Subtract the specified rectangle from the region.
+      </description>
+      <arg name="x" type="int" summary="region-local x coordinate"/>
+      <arg name="y" type="int" summary="region-local y coordinate"/>
+      <arg name="width" type="int" summary="rectangle width"/>
+      <arg name="height" type="int" summary="rectangle height"/>
+    </request>
+  </interface>
+
+  <interface name="wl_subcompositor" version="1">
+    <description summary="sub-surface compositing">
+      The global interface exposing sub-surface compositing capabilities.
+      A wl_surface, that has sub-surfaces associated, is called the
+      parent surface. Sub-surfaces can be arbitrarily nested and create
+      a tree of sub-surfaces.
+
+      The root surface in a tree of sub-surfaces is the main
+      surface. The main surface cannot be a sub-surface, because
+      sub-surfaces must always have a parent.
+
+      A main surface with its sub-surfaces forms a (compound) window.
+      For window management purposes, this set of wl_surface objects is
+      to be considered as a single window, and it should also behave as
+      such.
+
+      The aim of sub-surfaces is to offload some of the compositing work
+      within a window from clients to the compositor. A prime example is
+      a video player with decorations and video in separate wl_surface
+      objects. This should allow the compositor to pass YUV video buffer
+      processing to dedicated overlay hardware when possible.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="unbind from the subcompositor interface">
+       Informs the server that the client will not be using this
+       protocol object anymore. This does not affect any other
+       objects, wl_subsurface objects included.
+      </description>
+    </request>
+
+    <enum name="error">
+      <entry name="bad_surface" value="0"
+            summary="the to-be sub-surface is invalid"/>
+    </enum>
+
+    <request name="get_subsurface">
+      <description summary="give a surface the role sub-surface">
+       Create a sub-surface interface for the given surface, and
+       associate it with the given parent surface. This turns a
+       plain wl_surface into a sub-surface.
+
+       The to-be sub-surface must not already have another role, and it
+       must not have an existing wl_subsurface object. Otherwise a protocol
+       error is raised.
+      </description>
+      <arg name="id" type="new_id" interface="wl_subsurface"
+          summary="the new sub-surface object ID"/>
+      <arg name="surface" type="object" interface="wl_surface"
+          summary="the surface to be turned into a sub-surface"/>
+      <arg name="parent" type="object" interface="wl_surface"
+          summary="the parent surface"/>
+    </request>
+  </interface>
+
+  <interface name="wl_subsurface" version="1">
+    <description summary="sub-surface interface to a wl_surface">
+      An additional interface to a wl_surface object, which has been
+      made a sub-surface. A sub-surface has one parent surface. A
+      sub-surface's size and position are not limited to that of the parent.
+      Particularly, a sub-surface is not automatically clipped to its
+      parent's area.
+
+      A sub-surface becomes mapped, when a non-NULL wl_buffer is applied
+      and the parent surface is mapped. The order of which one happens
+      first is irrelevant. A sub-surface is hidden if the parent becomes
+      hidden, or if a NULL wl_buffer is applied. These rules apply
+      recursively through the tree of surfaces.
+
+      The behaviour of a wl_surface.commit request on a sub-surface
+      depends on the sub-surface's mode. The possible modes are
+      synchronized and desynchronized, see methods
+      wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized
+      mode caches the wl_surface state to be applied when the parent's
+      state gets applied, and desynchronized mode applies the pending
+      wl_surface state directly. A sub-surface is initially in the
+      synchronized mode.
+
+      Sub-surfaces have also other kind of state, which is managed by
+      wl_subsurface requests, as opposed to wl_surface requests. This
+      state includes the sub-surface position relative to the parent
+      surface (wl_subsurface.set_position), and the stacking order of
+      the parent and its sub-surfaces (wl_subsurface.place_above and
+      .place_below). This state is applied when the parent surface's
+      wl_surface state is applied, regardless of the sub-surface's mode.
+      As the exception, set_sync and set_desync are effective immediately.
+
+      The main surface can be thought to be always in desynchronized mode,
+      since it does not have a parent in the sub-surfaces sense.
+
+      Even if a sub-surface is in desynchronized mode, it will behave as
+      in synchronized mode, if its parent surface behaves as in
+      synchronized mode. This rule is applied recursively throughout the
+      tree of surfaces. This means, that one can set a sub-surface into
+      synchronized mode, and then assume that all its child and grand-child
+      sub-surfaces are synchronized, too, without explicitly setting them.
+
+      If the wl_surface associated with the wl_subsurface is destroyed, the
+      wl_subsurface object becomes inert. Note, that destroying either object
+      takes effect immediately. If you need to synchronize the removal
+      of a sub-surface to the parent surface update, unmap the sub-surface
+      first by attaching a NULL wl_buffer, update parent, and then destroy
+      the sub-surface.
+
+      If the parent wl_surface object is destroyed, the sub-surface is
+      unmapped.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="remove sub-surface interface">
+       The sub-surface interface is removed from the wl_surface object
+       that was turned into a sub-surface with a
+       wl_subcompositor.get_subsurface request. The wl_surface's association
+       to the parent is deleted, and the wl_surface loses its role as
+       a sub-surface. The wl_surface is unmapped.
+      </description>
+    </request>
+
+    <enum name="error">
+      <entry name="bad_surface" value="0"
+            summary="wl_surface is not a sibling or the parent"/>
+    </enum>
+
+    <request name="set_position">
+      <description summary="reposition the sub-surface">
+       This schedules a sub-surface position change.
+       The sub-surface will be moved so that its origin (top left
+       corner pixel) will be at the location x, y of the parent surface
+       coordinate system. The coordinates are not restricted to the parent
+       surface area. Negative values are allowed.
+
+       The scheduled coordinates will take effect whenever the state of the
+       parent surface is applied. When this happens depends on whether the
+       parent surface is in synchronized mode or not. See
+       wl_subsurface.set_sync and wl_subsurface.set_desync for details.
+
+       If more than one set_position request is invoked by the client before
+       the commit of the parent surface, the position of a new request always
+       replaces the scheduled position from any previous request.
+
+       The initial position is 0, 0.
+      </description>
+      <arg name="x" type="int" summary="x coordinate in the parent surface"/>
+      <arg name="y" type="int" summary="y coordinate in the parent surface"/>
+    </request>
+
+    <request name="place_above">
+      <description summary="restack the sub-surface">
+       This sub-surface is taken from the stack, and put back just
+       above the reference surface, changing the z-order of the sub-surfaces.
+       The reference surface must be one of the sibling surfaces, or the
+       parent surface. Using any other surface, including this sub-surface,
+       will cause a protocol error.
+
+       The z-order is double-buffered. Requests are handled in order and
+       applied immediately to a pending state. The final pending state is
+       copied to the active state the next time the state of the parent
+       surface is applied. When this happens depends on whether the parent
+       surface is in synchronized mode or not. See wl_subsurface.set_sync and
+       wl_subsurface.set_desync for details.
+
+       A new sub-surface is initially added as the top-most in the stack
+       of its siblings and parent.
+      </description>
+      <arg name="sibling" type="object" interface="wl_surface"
+          summary="the reference surface"/>
+    </request>
+
+    <request name="place_below">
+      <description summary="restack the sub-surface">
+       The sub-surface is placed just below the reference surface.
+       See wl_subsurface.place_above.
+      </description>
+      <arg name="sibling" type="object" interface="wl_surface"
+          summary="the reference surface"/>
+    </request>
+
+    <request name="set_sync">
+      <description summary="set sub-surface to synchronized mode">
+       Change the commit behaviour of the sub-surface to synchronized
+       mode, also described as the parent dependent mode.
+
+       In synchronized mode, wl_surface.commit on a sub-surface will
+       accumulate the committed state in a cache, but the state will
+       not be applied and hence will not change the compositor output.
+       The cached state is applied to the sub-surface immediately after
+       the parent surface's state is applied. This ensures atomic
+       updates of the parent and all its synchronized sub-surfaces.
+       Applying the cached state will invalidate the cache, so further
+       parent surface commits do not (re-)apply old state.
+
+       See wl_subsurface for the recursive effect of this mode.
+      </description>
+    </request>
+
+    <request name="set_desync">
+      <description summary="set sub-surface to desynchronized mode">
+       Change the commit behaviour of the sub-surface to desynchronized
+       mode, also described as independent or freely running mode.
+
+       In desynchronized mode, wl_surface.commit on a sub-surface will
+       apply the pending state directly, without caching, as happens
+       normally with a wl_surface. Calling wl_surface.commit on the
+       parent surface has no effect on the sub-surface's wl_surface
+       state. This mode allows a sub-surface to be updated on its own.
+
+       If cached state exists when wl_surface.commit is called in
+       desynchronized mode, the pending state is added to the cached
+       state, and applied as a whole. This invalidates the cache.
+
+       Note: even if a sub-surface is set to desynchronized, a parent
+       sub-surface may override it to behave as synchronized. For details,
+       see wl_subsurface.
+
+       If a surface's parent surface behaves as desynchronized, then
+       the cached state is applied on set_desync.
+      </description>
+    </request>
+  </interface>
+
+</protocol>
diff --git a/src/protocol/wlr-foreign-toplevel-management-unstable-v1.xml b/src/protocol/wlr-foreign-toplevel-management-unstable-v1.xml
new file mode 100644 (file)
index 0000000..a97738f
--- /dev/null
@@ -0,0 +1,259 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_foreign_toplevel_management_unstable_v1">
+  <copyright>
+    Copyright © 2018 Ilia Bozhinov
+
+    Permission to use, copy, modify, distribute, and sell this
+    software and its documentation for any purpose is hereby granted
+    without fee, provided that the above copyright notice appear in
+    all copies and that both that copyright notice and this permission
+    notice appear in supporting documentation, and that the name of
+    the copyright holders not be used in advertising or publicity
+    pertaining to distribution of the software without specific,
+    written prior permission.  The copyright holders make no
+    representations about the suitability of this software for any
+    purpose.  It is provided "as is" without express or implied
+    warranty.
+
+    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+    THIS SOFTWARE.
+  </copyright>
+
+  <interface name="zwlr_foreign_toplevel_manager_v1" version="2">
+    <description summary="list and control opened apps">
+      The purpose of this protocol is to enable the creation of taskbars
+      and docks by providing them with a list of opened applications and
+      letting them request certain actions on them, like maximizing, etc.
+
+      After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
+      toplevel window will be sent via the toplevel event
+    </description>
+
+    <event name="toplevel">
+      <description summary="a toplevel has been created">
+        This event is emitted whenever a new toplevel window is created. It
+        is emitted for all toplevels, regardless of the app that has created
+        them.
+
+        All initial details of the toplevel(title, app_id, states, etc.) will
+        be sent immediately after this event via the corresponding events in
+        zwlr_foreign_toplevel_handle_v1.
+      </description>
+      <arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
+    </event>
+
+    <request name="stop">
+      <description summary="stop sending events">
+        Indicates the client no longer wishes to receive events for new toplevels.
+        However the compositor may emit further toplevel_created events, until
+        the finished event is emitted.
+
+        The client must not send any more requests after this one.
+      </description>
+    </request>
+
+    <event name="finished">
+      <description summary="the compositor has finished with the toplevel manager">
+        This event indicates that the compositor is done sending events to the
+        zwlr_foreign_toplevel_manager_v1. The server will destroy the object
+        immediately after sending this request, so it will become invalid and
+        the client should free any resources associated with it.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="zwlr_foreign_toplevel_handle_v1" version="2">
+    <description summary="an opened toplevel">
+      A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
+      window. Each app may have multiple opened toplevels.
+
+      Each toplevel has a list of outputs it is visible on, conveyed to the
+      client with the output_enter and output_leave events.
+    </description>
+
+    <event name="title">
+      <description summary="title change">
+        This event is emitted whenever the title of the toplevel changes.
+      </description>
+      <arg name="title" type="string"/>
+    </event>
+
+    <event name="app_id">
+      <description summary="app-id change">
+        This event is emitted whenever the app-id of the toplevel changes.
+      </description>
+      <arg name="app_id" type="string"/>
+    </event>
+
+    <event name="output_enter">
+      <description summary="toplevel entered an output">
+        This event is emitted whenever the toplevel becomes visible on
+        the given output. A toplevel may be visible on multiple outputs.
+      </description>
+      <arg name="output" type="object" interface="wl_output"/>
+    </event>
+
+    <event name="output_leave">
+      <description summary="toplevel left an output">
+        This event is emitted whenever the toplevel stops being visible on
+        the given output. It is guaranteed that an entered-output event
+        with the same output has been emitted before this event.
+      </description>
+      <arg name="output" type="object" interface="wl_output"/>
+    </event>
+
+    <request name="set_maximized">
+      <description summary="requests that the toplevel be maximized">
+        Requests that the toplevel be maximized. If the maximized state actually
+        changes, this will be indicated by the state event.
+      </description>
+    </request>
+
+    <request name="unset_maximized">
+      <description summary="requests that the toplevel be unmaximized">
+        Requests that the toplevel be unmaximized. If the maximized state actually
+        changes, this will be indicated by the state event.
+      </description>
+    </request>
+
+    <request name="set_minimized">
+      <description summary="requests that the toplevel be minimized">
+        Requests that the toplevel be minimized. If the minimized state actually
+        changes, this will be indicated by the state event.
+      </description>
+    </request>
+
+    <request name="unset_minimized">
+      <description summary="requests that the toplevel be unminimized">
+        Requests that the toplevel be unminimized. If the minimized state actually
+        changes, this will be indicated by the state event.
+      </description>
+    </request>
+
+    <request name="activate">
+      <description summary="activate the toplevel">
+        Request that this toplevel be activated on the given seat.
+        There is no guarantee the toplevel will be actually activated.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat"/>
+    </request>
+
+    <enum name="state">
+      <description summary="types of states on the toplevel">
+        The different states that a toplevel can have. These have the same meaning
+        as the states with the same names defined in xdg-toplevel
+      </description>
+
+      <entry name="maximized"  value="0" summary="the toplevel is maximized"/>
+      <entry name="minimized"  value="1" summary="the toplevel is minimized"/>
+      <entry name="activated"  value="2" summary="the toplevel is active"/>
+      <entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
+    </enum>
+
+    <event name="state">
+      <description summary="the toplevel state changed">
+        This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
+        is created and each time the toplevel state changes, either because of a
+        compositor action or because of a request in this protocol.
+      </description>
+
+      <arg name="state" type="array"/>
+    </event>
+
+    <event name="done">
+      <description summary="all information about the toplevel has been sent">
+        This event is sent after all changes in the toplevel state have been
+        sent.
+
+        This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
+        to be seen as atomic, even if they happen via multiple events.
+      </description>
+    </event>
+
+    <request name="close">
+      <description summary="request that the toplevel be closed">
+        Send a request to the toplevel to close itself. The compositor would
+        typically use a shell-specific method to carry out this request, for
+        example by sending the xdg_toplevel.close event. However, this gives
+        no guarantees the toplevel will actually be destroyed. If and when
+        this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
+        be emitted.
+      </description>
+    </request>
+
+    <request name="set_rectangle">
+      <description summary="the rectangle which represents the toplevel">
+        The rectangle of the surface specified in this request corresponds to
+        the place where the app using this protocol represents the given toplevel.
+        It can be used by the compositor as a hint for some operations, e.g
+        minimizing. The client is however not required to set this, in which
+        case the compositor is free to decide some default value.
+
+        If the client specifies more than one rectangle, only the last one is
+        considered.
+
+        The dimensions are given in surface-local coordinates.
+        Setting width=height=0 removes the already-set rectangle.
+      </description>
+
+      <arg name="surface" type="object" interface="wl_surface"/>
+      <arg name="x" type="int"/>
+      <arg name="y" type="int"/>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+    </request>
+
+    <enum name="error">
+      <entry name="invalid_rectangle" value="0"
+        summary="the provided rectangle is invalid"/>
+    </enum>
+
+    <event name="closed">
+      <description summary="this toplevel has been destroyed">
+        This event means the toplevel has been destroyed. It is guaranteed there
+        won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
+        toplevel itself becomes inert so any requests will be ignored except the
+        destroy request.
+      </description>
+    </event>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
+        Destroys the zwlr_foreign_toplevel_handle_v1 object.
+
+        This request should be called either when the client does not want to
+        use the toplevel anymore or after the closed event to finalize the
+        destruction of the object.
+      </description>
+    </request>
+
+    <!-- Version 2 additions -->
+
+    <request name="set_fullscreen" since="2">
+      <description summary="request that the toplevel be fullscreened">
+        Requests that the toplevel be fullscreened on the given output. If the
+        fullscreen state and/or the outputs the toplevel is visible on actually
+        change, this will be indicated by the state and output_enter/leave
+        events.
+
+        The output parameter is only a hint to the compositor. Also, if output
+        is NULL, the compositor should decide which output the toplevel will be
+        fullscreened on, if at all.
+      </description>
+      <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+    </request>
+
+    <request name="unset_fullscreen" since="2">
+      <description summary="request that the toplevel be unfullscreened">
+        Requests that the toplevel be unfullscreened. If the fullscreen state
+        actually changes, this will be indicated by the state event.
+      </description>
+    </request>
+  </interface>
+</protocol>
diff --git a/src/protocol/wlr-layer-shell-unstable-v1.xml b/src/protocol/wlr-layer-shell-unstable-v1.xml
new file mode 100644 (file)
index 0000000..6a5d5d3
--- /dev/null
@@ -0,0 +1,285 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_layer_shell_unstable_v1">
+  <copyright>
+    Copyright © 2017 Drew DeVault
+
+    Permission to use, copy, modify, distribute, and sell this
+    software and its documentation for any purpose is hereby granted
+    without fee, provided that the above copyright notice appear in
+    all copies and that both that copyright notice and this permission
+    notice appear in supporting documentation, and that the name of
+    the copyright holders not be used in advertising or publicity
+    pertaining to distribution of the software without specific,
+    written prior permission.  The copyright holders make no
+    representations about the suitability of this software for any
+    purpose.  It is provided "as is" without express or implied
+    warranty.
+
+    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+    THIS SOFTWARE.
+  </copyright>
+
+  <interface name="zwlr_layer_shell_v1" version="1">
+    <description summary="create surfaces that are layers of the desktop">
+      Clients can use this interface to assign the surface_layer role to
+      wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+      rendered with a defined z-depth respective to each other. They may also be
+      anchored to the edges and corners of a screen and specify input handling
+      semantics. This interface should be suitable for the implementation of
+      many desktop shell components, and a broad number of other applications
+      that interact with the desktop.
+    </description>
+
+    <request name="get_layer_surface">
+      <description summary="create a layer_surface from a surface">
+        Create a layer surface for an existing surface. This assigns the role of
+        layer_surface, or raises a protocol error if another role is already
+        assigned.
+
+        Creating a layer surface from a wl_surface which has a buffer attached
+        or committed is a client error, and any attempts by a client to attach
+        or manipulate a buffer prior to the first layer_surface.configure call
+        must also be treated as errors.
+
+        You may pass NULL for output to allow the compositor to decide which
+        output to use. Generally this will be the one that the user most
+        recently interacted with.
+
+        Clients can specify a namespace that defines the purpose of the layer
+        surface.
+      </description>
+      <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
+      <arg name="surface" type="object" interface="wl_surface"/>
+      <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+      <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
+      <arg name="namespace" type="string" summary="namespace for the layer surface"/>
+    </request>
+
+    <enum name="error">
+      <entry name="role" value="0" summary="wl_surface has another role"/>
+      <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
+      <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
+    </enum>
+
+    <enum name="layer">
+      <description summary="available layers for surfaces">
+        These values indicate which layers a surface can be rendered in. They
+        are ordered by z depth, bottom-most first. Traditional shell surfaces
+        will typically be rendered between the bottom and top layers.
+        Fullscreen shell surfaces are typically rendered at the top layer.
+        Multiple surfaces can share a single layer, and ordering within a
+        single layer is undefined.
+      </description>
+
+      <entry name="background" value="0"/>
+      <entry name="bottom" value="1"/>
+      <entry name="top" value="2"/>
+      <entry name="overlay" value="3"/>
+    </enum>
+  </interface>
+
+  <interface name="zwlr_layer_surface_v1" version="1">
+    <description summary="layer metadata interface">
+      An interface that may be implemented by a wl_surface, for surfaces that
+      are designed to be rendered as a layer of a stacked desktop-like
+      environment.
+
+      Layer surface state (size, anchor, exclusive zone, margin, interactivity)
+      is double-buffered, and will be applied at the time wl_surface.commit of
+      the corresponding wl_surface is called.
+    </description>
+
+    <request name="set_size">
+      <description summary="sets the size of the surface">
+        Sets the size of the surface in surface-local coordinates. The
+        compositor will display the surface centered with respect to its
+        anchors.
+
+        If you pass 0 for either value, the compositor will assign it and
+        inform you of the assignment in the configure event. You must set your
+        anchor to opposite edges in the dimensions you omit; not doing so is a
+        protocol error. Both values are 0 by default.
+
+        Size is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="width" type="uint"/>
+      <arg name="height" type="uint"/>
+    </request>
+
+    <request name="set_anchor">
+      <description summary="configures the anchor point of the surface">
+        Requests that the compositor anchor the surface to the specified edges
+        and corners. If two orthoginal edges are specified (e.g. 'top' and
+        'left'), then the anchor point will be the intersection of the edges
+        (e.g. the top left corner of the output); otherwise the anchor point
+        will be centered on that edge, or in the center if none is specified.
+
+        Anchor is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="anchor" type="uint" enum="anchor"/>
+    </request>
+
+    <request name="set_exclusive_zone">
+      <description summary="configures the exclusive geometry of this surface">
+        Requests that the compositor avoids occluding an area of the surface
+        with other surfaces. The compositor's use of this information is
+        implementation-dependent - do not assume that this region will not
+        actually be occluded.
+
+        A positive value is only meaningful if the surface is anchored to an
+        edge, rather than a corner. The zone is the number of surface-local
+        coordinates from the edge that are considered exclusive.
+
+        Surfaces that do not wish to have an exclusive zone may instead specify
+        how they should interact with surfaces that do. If set to zero, the
+        surface indicates that it would like to be moved to avoid occluding
+        surfaces with a positive excluzive zone. If set to -1, the surface
+        indicates that it would not like to be moved to accomodate for other
+        surfaces, and the compositor should extend it all the way to the edges
+        it is anchored to.
+
+        For example, a panel might set its exclusive zone to 10, so that
+        maximized shell surfaces are not shown on top of it. A notification
+        might set its exclusive zone to 0, so that it is moved to avoid
+        occluding the panel, but shell surfaces are shown underneath it. A
+        wallpaper or lock screen might set their exclusive zone to -1, so that
+        they stretch below or over the panel.
+
+        The default value is 0.
+
+        Exclusive zone is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="zone" type="int"/>
+    </request>
+
+    <request name="set_margin">
+      <description summary="sets a margin from the anchor point">
+        Requests that the surface be placed some distance away from the anchor
+        point on the output, in surface-local coordinates. Setting this value
+        for edges you are not anchored to has no effect.
+
+        The exclusive zone includes the margin.
+
+        Margin is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="top" type="int"/>
+      <arg name="right" type="int"/>
+      <arg name="bottom" type="int"/>
+      <arg name="left" type="int"/>
+    </request>
+
+    <request name="set_keyboard_interactivity">
+      <description summary="requests keyboard events">
+        Set to 1 to request that the seat send keyboard events to this layer
+        surface. For layers below the shell surface layer, the seat will use
+        normal focus semantics. For layers above the shell surface layers, the
+        seat will always give exclusive keyboard focus to the top-most layer
+        which has keyboard interactivity set to true.
+
+        Layer surfaces receive pointer, touch, and tablet events normally. If
+        you do not want to receive them, set the input region on your surface
+        to an empty region.
+
+        Events is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="keyboard_interactivity" type="uint"/>
+    </request>
+
+    <request name="get_popup">
+      <description summary="assign this layer_surface as an xdg_popup parent">
+        This assigns an xdg_popup's parent to this layer_surface.  This popup
+        should have been created via xdg_surface::get_popup with the parent set
+        to NULL, and this request must be invoked before committing the popup's
+        initial state.
+
+        See the documentation of xdg_popup for more details about what an
+        xdg_popup is and how it is used.
+      </description>
+      <arg name="popup" type="object" interface="xdg_popup"/>
+    </request>
+
+    <request name="ack_configure">
+      <description summary="ack a configure event">
+        When a configure event is received, if a client commits the
+        surface in response to the configure event, then the client
+        must make an ack_configure request sometime before the commit
+        request, passing along the serial of the configure event.
+
+        If the client receives multiple configure events before it
+        can respond to one, it only has to ack the last configure event.
+
+        A client is not required to commit immediately after sending
+        an ack_configure request - it may even ack_configure several times
+        before its next surface commit.
+
+        A client may send multiple ack_configure requests before committing, but
+        only the last request sent before a commit indicates which configure
+        event the client really is responding to.
+      </description>
+      <arg name="serial" type="uint" summary="the serial from the configure event"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the layer_surface">
+        This request destroys the layer surface.
+      </description>
+    </request>
+
+    <event name="configure">
+      <description summary="suggest a surface change">
+        The configure event asks the client to resize its surface.
+
+        Clients should arrange their surface for the new states, and then send
+        an ack_configure request with the serial sent in this configure event at
+        some point before committing the new surface.
+
+        The client is free to dismiss all but the last configure event it
+        received.
+
+        The width and height arguments specify the size of the window in
+        surface-local coordinates.
+
+        The size is a hint, in the sense that the client is free to ignore it if
+        it doesn't resize, pick a smaller size (to satisfy aspect ratio or
+        resize in steps of NxM pixels). If the client picks a smaller size and
+        is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
+        surface will be centered on this axis.
+
+        If the width or height arguments are zero, it means the client should
+        decide its own window dimension.
+      </description>
+      <arg name="serial" type="uint"/>
+      <arg name="width" type="uint"/>
+      <arg name="height" type="uint"/>
+    </event>
+
+    <event name="closed">
+      <description summary="surface should be closed">
+        The closed event is sent by the compositor when the surface will no
+        longer be shown. The output may have been destroyed or the user may
+        have asked for it to be removed. Further changes to the surface will be
+        ignored. The client should destroy the resource after receiving this
+        event, and create a new surface if they so choose.
+      </description>
+    </event>
+
+    <enum name="error">
+      <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
+      <entry name="invalid_size" value="1" summary="size is invalid"/>
+      <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
+    </enum>
+
+    <enum name="anchor" bitfield="true">
+      <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
+      <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
+      <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
+      <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
+    </enum>
+  </interface>
+</protocol>
diff --git a/src/protocol/xdg-output-unstable-v1.xml b/src/protocol/xdg-output-unstable-v1.xml
new file mode 100644 (file)
index 0000000..fe3a70a
--- /dev/null
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_output_unstable_v1">
+
+  <copyright>
+    Copyright © 2017 Red Hat Inc.
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="Protocol to describe output regions">
+    This protocol aims at describing outputs in a way which is more in line
+    with the concept of an output on desktop oriented systems.
+
+    Some information are more specific to the concept of an output for
+    a desktop oriented system and may not make sense in other applications,
+    such as IVI systems for example.
+
+    Typically, the global compositor space on a desktop system is made of
+    a contiguous or overlapping set of rectangular regions.
+
+    Some of the information provided in this protocol might be identical
+    to their counterparts already available from wl_output, in which case
+    the information provided by this protocol should be preferred to their
+    equivalent in wl_output. The goal is to move the desktop specific
+    concepts (such as output location within the global compositor space,
+    the connector name and types, etc.) out of the core wl_output protocol.
+
+    Warning! The protocol described in this file is experimental and
+    backward incompatible changes may be made. Backward compatible
+    changes may be added together with the corresponding interface
+    version bump.
+    Backward incompatible changes are done by bumping the version
+    number in the protocol and interface names and resetting the
+    interface version. Once the protocol is to be declared stable,
+    the 'z' prefix and the version number in the protocol and
+    interface names are removed and the interface version number is
+    reset.
+  </description>
+
+  <interface name="zxdg_output_manager_v1" version="3">
+    <description summary="manage xdg_output objects">
+      A global factory interface for xdg_output objects.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the xdg_output_manager object">
+       Using this request a client can tell the server that it is not
+       going to use the xdg_output_manager object anymore.
+
+       Any objects already created through this instance are not affected.
+      </description>
+    </request>
+
+    <request name="get_xdg_output">
+      <description summary="create an xdg output from a wl_output">
+       This creates a new xdg_output object for the given wl_output.
+      </description>
+      <arg name="id" type="new_id" interface="zxdg_output_v1"/>
+      <arg name="output" type="object" interface="wl_output"/>
+    </request>
+  </interface>
+
+  <interface name="zxdg_output_v1" version="3">
+    <description summary="compositor logical output region">
+      An xdg_output describes part of the compositor geometry.
+
+      This typically corresponds to a monitor that displays part of the
+      compositor space.
+
+      For objects version 3 onwards, after all xdg_output properties have been
+      sent (when the object is created and when properties are updated), a
+      wl_output.done event is sent. This allows changes to the output
+      properties to be seen as atomic, even if they happen via multiple events.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the xdg_output object">
+       Using this request a client can tell the server that it is not
+       going to use the xdg_output object anymore.
+      </description>
+    </request>
+
+    <event name="logical_position">
+      <description summary="position of the output within the global compositor space">
+       The position event describes the location of the wl_output within
+       the global compositor space.
+
+       The logical_position event is sent after creating an xdg_output
+       (see xdg_output_manager.get_xdg_output) and whenever the location
+       of the output changes within the global compositor space.
+      </description>
+      <arg name="x" type="int"
+          summary="x position within the global compositor space"/>
+      <arg name="y" type="int"
+          summary="y position within the global compositor space"/>
+    </event>
+
+    <event name="logical_size">
+      <description summary="size of the output in the global compositor space">
+       The logical_size event describes the size of the output in the
+       global compositor space.
+
+       For example, a surface without any buffer scale, transformation
+       nor rotation set, with the size matching the logical_size will
+       have the same size as the corresponding output when displayed.
+
+       Most regular Wayland clients should not pay attention to the
+       logical size and would rather rely on xdg_shell interfaces.
+
+       Some clients such as Xwayland, however, need this to configure
+       their surfaces in the global compositor space as the compositor
+       may apply a different scale from what is advertised by the output
+       scaling property (to achieve fractional scaling, for example).
+
+       For example, for a wl_output mode 3840×2160 and a scale factor 2:
+
+       - A compositor not scaling the surface buffers will advertise a
+         logical size of 3840×2160,
+
+       - A compositor automatically scaling the surface buffers will
+         advertise a logical size of 1920×1080,
+
+       - A compositor using a fractional scale of 1.5 will advertise a
+         logical size to 2560×1620.
+
+       For example, for a wl_output mode 1920×1080 and a 90 degree rotation,
+       the compositor will advertise a logical size of 1080x1920.
+
+       The logical_size event is sent after creating an xdg_output
+       (see xdg_output_manager.get_xdg_output) and whenever the logical
+       size of the output changes, either as a result of a change in the
+       applied scale or because of a change in the corresponding output
+       mode(see wl_output.mode) or transform (see wl_output.transform).
+      </description>
+      <arg name="width" type="int"
+          summary="width in global compositor space"/>
+      <arg name="height" type="int"
+          summary="height in global compositor space"/>
+    </event>
+
+    <event name="done">
+      <description summary="all information about the output have been sent">
+       This event is sent after all other properties of an xdg_output
+       have been sent.
+
+       This allows changes to the xdg_output properties to be seen as
+       atomic, even if they happen via multiple events.
+
+       For objects version 3 onwards, this event is deprecated. Compositors
+       are not required to send it anymore and must send wl_output.done
+       instead.
+      </description>
+    </event>
+
+    <!-- Version 2 additions -->
+
+    <event name="name" since="2">
+      <description summary="name of this output">
+       Many compositors will assign names to their outputs, show them to the
+       user, allow them to be configured by name, etc. The client may wish to
+       know this name as well to offer the user similar behaviors.
+
+       The naming convention is compositor defined, but limited to
+       alphanumeric characters and dashes (-). Each name is unique among all
+       wl_output globals, but if a wl_output global is destroyed the same name
+       may be reused later. The names will also remain consistent across
+       sessions with the same hardware and software configuration.
+
+       Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
+       not assume that the name is a reflection of an underlying DRM
+       connector, X11 connection, etc.
+
+       The name event is sent after creating an xdg_output (see
+       xdg_output_manager.get_xdg_output). This event is only sent once per
+       xdg_output, and the name does not change over the lifetime of the
+       wl_output global.
+      </description>
+      <arg name="name" type="string" summary="output name"/>
+    </event>
+
+    <event name="description" since="2">
+      <description summary="human-readable description of this output">
+       Many compositors can produce human-readable descriptions of their
+       outputs.  The client may wish to know this description as well, to
+       communicate the user for various purposes.
+
+       The description is a UTF-8 string with no convention defined for its
+       contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11
+       output via :1'.
+
+       The description event is sent after creating an xdg_output (see
+       xdg_output_manager.get_xdg_output) and whenever the description
+       changes. The description is optional, and may not be sent at all.
+
+       For objects of version 2 and lower, this event is only sent once per
+       xdg_output, and the description does not change over the lifetime of
+       the wl_output global.
+      </description>
+      <arg name="description" type="string" summary="output description"/>
+    </event>
+
+  </interface>
+</protocol>
diff --git a/src/protocol/xdg-shell-unstable-v6.xml b/src/protocol/xdg-shell-unstable-v6.xml
new file mode 100644 (file)
index 0000000..1c0f924
--- /dev/null
@@ -0,0 +1,1044 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_shell_unstable_v6">
+
+  <copyright>
+    Copyright © 2008-2013 Kristian Høgsberg
+    Copyright © 2013      Rafael Antognolli
+    Copyright © 2013      Jasper St. Pierre
+    Copyright © 2010-2013 Intel Corporation
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <interface name="zxdg_shell_v6" version="1">
+    <description summary="create desktop-style surfaces">
+      xdg_shell allows clients to turn a wl_surface into a "real window"
+      which can be dragged, resized, stacked, and moved around by the
+      user. Everything about this interface is suited towards traditional
+      desktop environments.
+    </description>
+
+    <enum name="error">
+      <entry name="role" value="0" summary="given wl_surface has another role"/>
+      <entry name="defunct_surfaces" value="1"
+            summary="xdg_shell was destroyed before children"/>
+      <entry name="not_the_topmost_popup" value="2"
+            summary="the client tried to map or destroy a non-topmost popup"/>
+      <entry name="invalid_popup_parent" value="3"
+            summary="the client specified an invalid popup parent surface"/>
+      <entry name="invalid_surface_state" value="4"
+            summary="the client provided an invalid surface state"/>
+      <entry name="invalid_positioner" value="5"
+            summary="the client provided an invalid positioner"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy xdg_shell">
+       Destroy this xdg_shell object.
+
+       Destroying a bound xdg_shell object while there are surfaces
+       still alive created by this xdg_shell object instance is illegal
+       and will result in a protocol error.
+      </description>
+    </request>
+
+    <request name="create_positioner">
+      <description summary="create a positioner object">
+       Create a positioner object. A positioner object is used to position
+       surfaces relative to some parent surface. See the interface description
+       and xdg_surface.get_popup for details.
+      </description>
+      <arg name="id" type="new_id" interface="zxdg_positioner_v6"/>
+    </request>
+
+    <request name="get_xdg_surface">
+      <description summary="create a shell surface from a surface">
+       This creates an xdg_surface for the given surface. While xdg_surface
+       itself is not a role, the corresponding surface may only be assigned
+       a role extending xdg_surface, such as xdg_toplevel or xdg_popup.
+
+       This creates an xdg_surface for the given surface. An xdg_surface is
+       used as basis to define a role to a given surface, such as xdg_toplevel
+       or xdg_popup. It also manages functionality shared between xdg_surface
+       based surface roles.
+
+       See the documentation of xdg_surface for more details about what an
+       xdg_surface is and how it is used.
+      </description>
+      <arg name="id" type="new_id" interface="zxdg_surface_v6"/>
+      <arg name="surface" type="object" interface="wl_surface"/>
+    </request>
+
+    <request name="pong">
+      <description summary="respond to a ping event">
+       A client must respond to a ping event with a pong request or
+       the client may be deemed unresponsive. See xdg_shell.ping.
+      </description>
+      <arg name="serial" type="uint" summary="serial of the ping event"/>
+    </request>
+
+    <event name="ping">
+      <description summary="check if the client is alive">
+       The ping event asks the client if it's still alive. Pass the
+       serial specified in the event back to the compositor by sending
+       a "pong" request back with the specified serial. See xdg_shell.ping.
+
+       Compositors can use this to determine if the client is still
+       alive. It's unspecified what will happen if the client doesn't
+       respond to the ping request, or in what timeframe. Clients should
+       try to respond in a reasonable amount of time.
+
+       A compositor is free to ping in any way it wants, but a client must
+       always respond to any xdg_shell object it created.
+      </description>
+      <arg name="serial" type="uint" summary="pass this to the pong request"/>
+    </event>
+  </interface>
+
+  <interface name="zxdg_positioner_v6" version="1">
+    <description summary="child surface positioner">
+      The xdg_positioner provides a collection of rules for the placement of a
+      child surface relative to a parent surface. Rules can be defined to ensure
+      the child surface remains within the visible area's borders, and to
+      specify how the child surface changes its position, such as sliding along
+      an axis, or flipping around a rectangle. These positioner-created rules are
+      constrained by the requirement that a child surface must intersect with or
+      be at least partially adjacent to its parent surface.
+
+      See the various requests for details about possible rules.
+
+      At the time of the request, the compositor makes a copy of the rules
+      specified by the xdg_positioner. Thus, after the request is complete the
+      xdg_positioner object can be destroyed or reused; further changes to the
+      object will have no effect on previous usages.
+
+      For an xdg_positioner object to be considered complete, it must have a
+      non-zero size set by set_size, and a non-zero anchor rectangle set by
+      set_anchor_rect. Passing an incomplete xdg_positioner object when
+      positioning a surface raises an error.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_input" value="0" summary="invalid input provided"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the xdg_positioner object">
+       Notify the compositor that the xdg_positioner will no longer be used.
+      </description>
+    </request>
+
+    <request name="set_size">
+      <description summary="set the size of the to-be positioned rectangle">
+       Set the size of the surface that is to be positioned with the positioner
+       object. The size is in surface-local coordinates and corresponds to the
+       window geometry. See xdg_surface.set_window_geometry.
+
+       If a zero or negative size is set the invalid_input error is raised.
+      </description>
+      <arg name="width" type="int" summary="width of positioned rectangle"/>
+      <arg name="height" type="int" summary="height of positioned rectangle"/>
+    </request>
+
+    <request name="set_anchor_rect">
+      <description summary="set the anchor rectangle within the parent surface">
+       Specify the anchor rectangle within the parent surface that the child
+       surface will be placed relative to. The rectangle is relative to the
+       window geometry as defined by xdg_surface.set_window_geometry of the
+       parent surface. The rectangle must be at least 1x1 large.
+
+       When the xdg_positioner object is used to position a child surface, the
+       anchor rectangle may not extend outside the window geometry of the
+       positioned child's parent surface.
+
+       If a zero or negative size is set the invalid_input error is raised.
+      </description>
+      <arg name="x" type="int" summary="x position of anchor rectangle"/>
+      <arg name="y" type="int" summary="y position of anchor rectangle"/>
+      <arg name="width" type="int" summary="width of anchor rectangle"/>
+      <arg name="height" type="int" summary="height of anchor rectangle"/>
+    </request>
+
+    <enum name="anchor" bitfield="true">
+      <entry name="none" value="0"
+            summary="the center of the anchor rectangle"/>
+      <entry name="top" value="1"
+            summary="the top edge of the anchor rectangle"/>
+      <entry name="bottom" value="2"
+            summary="the bottom edge of the anchor rectangle"/>
+      <entry name="left" value="4"
+            summary="the left edge of the anchor rectangle"/>
+      <entry name="right" value="8"
+            summary="the right edge of the anchor rectangle"/>
+    </enum>
+
+    <request name="set_anchor">
+      <description summary="set anchor rectangle anchor edges">
+       Defines a set of edges for the anchor rectangle. These are used to
+       derive an anchor point that the child surface will be positioned
+       relative to. If two orthogonal edges are specified (e.g. 'top' and
+       'left'), then the anchor point will be the intersection of the edges
+       (e.g. the top left position of the rectangle); otherwise, the derived
+       anchor point will be centered on the specified edge, or in the center of
+       the anchor rectangle if no edge is specified.
+
+       If two parallel anchor edges are specified (e.g. 'left' and 'right'),
+       the invalid_input error is raised.
+      </description>
+      <arg name="anchor" type="uint" enum="anchor"
+          summary="bit mask of anchor edges"/>
+    </request>
+
+    <enum name="gravity" bitfield="true">
+      <entry name="none" value="0"
+            summary="center over the anchor edge"/>
+      <entry name="top" value="1"
+            summary="position above the anchor edge"/>
+      <entry name="bottom" value="2"
+            summary="position below the anchor edge"/>
+      <entry name="left" value="4"
+            summary="position to the left of the anchor edge"/>
+      <entry name="right" value="8"
+            summary="position to the right of the anchor edge"/>
+    </enum>
+
+    <request name="set_gravity">
+      <description summary="set child surface gravity">
+       Defines in what direction a surface should be positioned, relative to
+       the anchor point of the parent surface. If two orthogonal gravities are
+       specified (e.g. 'bottom' and 'right'), then the child surface will be
+       placed in the specified direction; otherwise, the child surface will be
+       centered over the anchor point on any axis that had no gravity
+       specified.
+
+       If two parallel gravities are specified (e.g. 'left' and 'right'), the
+       invalid_input error is raised.
+      </description>
+      <arg name="gravity" type="uint" enum="gravity"
+          summary="bit mask of gravity directions"/>
+    </request>
+
+    <enum name="constraint_adjustment" bitfield="true">
+      <description summary="constraint adjustments">
+       The constraint adjustment value define ways the compositor will adjust
+       the position of the surface, if the unadjusted position would result
+       in the surface being partly constrained.
+
+       Whether a surface is considered 'constrained' is left to the compositor
+       to determine. For example, the surface may be partly outside the
+       compositor's defined 'work area', thus necessitating the child surface's
+       position be adjusted until it is entirely inside the work area.
+
+       The adjustments can be combined, according to a defined precedence: 1)
+       Flip, 2) Slide, 3) Resize.
+      </description>
+      <entry name="none" value="0">
+       <description summary="don't move the child surface when constrained">
+         Don't alter the surface position even if it is constrained on some
+         axis, for example partially outside the edge of a monitor.
+       </description>
+      </entry>
+      <entry name="slide_x" value="1">
+       <description summary="move along the x axis until unconstrained">
+         Slide the surface along the x axis until it is no longer constrained.
+
+         First try to slide towards the direction of the gravity on the x axis
+         until either the edge in the opposite direction of the gravity is
+         unconstrained or the edge in the direction of the gravity is
+         constrained.
+
+         Then try to slide towards the opposite direction of the gravity on the
+         x axis until either the edge in the direction of the gravity is
+         unconstrained or the edge in the opposite direction of the gravity is
+         constrained.
+       </description>
+      </entry>
+      <entry name="slide_y" value="2">
+       <description summary="move along the y axis until unconstrained">
+         Slide the surface along the y axis until it is no longer constrained.
+
+         First try to slide towards the direction of the gravity on the y axis
+         until either the edge in the opposite direction of the gravity is
+         unconstrained or the edge in the direction of the gravity is
+         constrained.
+
+         Then try to slide towards the opposite direction of the gravity on the
+         y axis until either the edge in the direction of the gravity is
+         unconstrained or the edge in the opposite direction of the gravity is
+         constrained.
+       </description>
+      </entry>
+      <entry name="flip_x" value="4">
+       <description summary="invert the anchor and gravity on the x axis">
+         Invert the anchor and gravity on the x axis if the surface is
+         constrained on the x axis. For example, if the left edge of the
+         surface is constrained, the gravity is 'left' and the anchor is
+         'left', change the gravity to 'right' and the anchor to 'right'.
+
+         If the adjusted position also ends up being constrained, the resulting
+         position of the flip_x adjustment will be the one before the
+         adjustment.
+       </description>
+      </entry>
+      <entry name="flip_y" value="8">
+       <description summary="invert the anchor and gravity on the y axis">
+         Invert the anchor and gravity on the y axis if the surface is
+         constrained on the y axis. For example, if the bottom edge of the
+         surface is constrained, the gravity is 'bottom' and the anchor is
+         'bottom', change the gravity to 'top' and the anchor to 'top'.
+
+         If the adjusted position also ends up being constrained, the resulting
+         position of the flip_y adjustment will be the one before the
+         adjustment.
+       </description>
+      </entry>
+      <entry name="resize_x" value="16">
+       <description summary="horizontally resize the surface">
+         Resize the surface horizontally so that it is completely
+         unconstrained.
+       </description>
+      </entry>
+      <entry name="resize_y" value="32">
+       <description summary="vertically resize the surface">
+         Resize the surface vertically so that it is completely unconstrained.
+       </description>
+      </entry>
+    </enum>
+
+    <request name="set_constraint_adjustment">
+      <description summary="set the adjustment to be done when constrained">
+       Specify how the window should be positioned if the originally intended
+       position caused the surface to be constrained, meaning at least
+       partially outside positioning boundaries set by the compositor. The
+       adjustment is set by constructing a bitmask describing the adjustment to
+       be made when the surface is constrained on that axis.
+
+       If no bit for one axis is set, the compositor will assume that the child
+       surface should not change its position on that axis when constrained.
+
+       If more than one bit for one axis is set, the order of how adjustments
+       are applied is specified in the corresponding adjustment descriptions.
+
+       The default adjustment is none.
+      </description>
+      <arg name="constraint_adjustment" type="uint"
+          summary="bit mask of constraint adjustments"/>
+    </request>
+
+    <request name="set_offset">
+      <description summary="set surface position offset">
+       Specify the surface position offset relative to the position of the
+       anchor on the anchor rectangle and the anchor on the surface. For
+       example if the anchor of the anchor rectangle is at (x, y), the surface
+       has the gravity bottom|right, and the offset is (ox, oy), the calculated
+       surface position will be (x + ox, y + oy). The offset position of the
+       surface is the one used for constraint testing. See
+       set_constraint_adjustment.
+
+       An example use case is placing a popup menu on top of a user interface
+       element, while aligning the user interface element of the parent surface
+       with some user interface element placed somewhere in the popup surface.
+      </description>
+      <arg name="x" type="int" summary="surface position x offset"/>
+      <arg name="y" type="int" summary="surface position y offset"/>
+    </request>
+  </interface>
+
+  <interface name="zxdg_surface_v6" version="1">
+    <description summary="desktop user interface surface base interface">
+      An interface that may be implemented by a wl_surface, for
+      implementations that provide a desktop-style user interface.
+
+      It provides a base set of functionality required to construct user
+      interface elements requiring management by the compositor, such as
+      toplevel windows, menus, etc. The types of functionality are split into
+      xdg_surface roles.
+
+      Creating an xdg_surface does not set the role for a wl_surface. In order
+      to map an xdg_surface, the client must create a role-specific object
+      using, e.g., get_toplevel, get_popup. The wl_surface for any given
+      xdg_surface can have at most one role, and may not be assigned any role
+      not based on xdg_surface.
+
+      A role must be assigned before any other requests are made to the
+      xdg_surface object.
+
+      The client must call wl_surface.commit on the corresponding wl_surface
+      for the xdg_surface state to take effect.
+
+      Creating an xdg_surface from a wl_surface which has a buffer attached or
+      committed is a client error, and any attempts by a client to attach or
+      manipulate a buffer prior to the first xdg_surface.configure call must
+      also be treated as errors.
+
+      For a surface to be mapped by the compositor, the following conditions
+      must be met: (1) the client has assigned a xdg_surface based role to the
+      surface, (2) the client has set and committed the xdg_surface state and
+      the role dependent state to the surface and (3) the client has committed a
+      buffer to the surface.
+    </description>
+
+    <enum name="error">
+      <entry name="not_constructed" value="1"/>
+      <entry name="already_constructed" value="2"/>
+      <entry name="unconfigured_buffer" value="3"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the xdg_surface">
+       Destroy the xdg_surface object. An xdg_surface must only be destroyed
+       after its role object has been destroyed.
+      </description>
+    </request>
+
+    <request name="get_toplevel">
+      <description summary="assign the xdg_toplevel surface role">
+       This creates an xdg_toplevel object for the given xdg_surface and gives
+       the associated wl_surface the xdg_toplevel role.
+
+       See the documentation of xdg_toplevel for more details about what an
+       xdg_toplevel is and how it is used.
+      </description>
+      <arg name="id" type="new_id" interface="zxdg_toplevel_v6"/>
+    </request>
+
+    <request name="get_popup">
+      <description summary="assign the xdg_popup surface role">
+       This creates an xdg_popup object for the given xdg_surface and gives the
+       associated wl_surface the xdg_popup role.
+
+       See the documentation of xdg_popup for more details about what an
+       xdg_popup is and how it is used.
+      </description>
+      <arg name="id" type="new_id" interface="zxdg_popup_v6"/>
+      <arg name="parent" type="object" interface="zxdg_surface_v6"/>
+      <arg name="positioner" type="object" interface="zxdg_positioner_v6"/>
+    </request>
+
+    <request name="set_window_geometry">
+      <description summary="set the new window geometry">
+       The window geometry of a surface is its "visible bounds" from the
+       user's perspective. Client-side decorations often have invisible
+       portions like drop-shadows which should be ignored for the
+       purposes of aligning, placing and constraining windows.
+
+       The window geometry is double buffered, and will be applied at the
+       time wl_surface.commit of the corresponding wl_surface is called.
+
+       Once the window geometry of the surface is set, it is not possible to
+       unset it, and it will remain the same until set_window_geometry is
+       called again, even if a new subsurface or buffer is attached.
+
+       If never set, the value is the full bounds of the surface,
+       including any subsurfaces. This updates dynamically on every
+       commit. This unset is meant for extremely simple clients.
+
+       The arguments are given in the surface-local coordinate space of
+       the wl_surface associated with this xdg_surface.
+
+       The width and height must be greater than zero. Setting an invalid size
+       will raise an error. When applied, the effective window geometry will be
+       the set window geometry clamped to the bounding rectangle of the
+       combined geometry of the surface of the xdg_surface and the associated
+       subsurfaces.
+      </description>
+      <arg name="x" type="int"/>
+      <arg name="y" type="int"/>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+    </request>
+
+    <request name="ack_configure">
+      <description summary="ack a configure event">
+       When a configure event is received, if a client commits the
+       surface in response to the configure event, then the client
+       must make an ack_configure request sometime before the commit
+       request, passing along the serial of the configure event.
+
+       For instance, for toplevel surfaces the compositor might use this
+       information to move a surface to the top left only when the client has
+       drawn itself for the maximized or fullscreen state.
+
+       If the client receives multiple configure events before it
+       can respond to one, it only has to ack the last configure event.
+
+       A client is not required to commit immediately after sending
+       an ack_configure request - it may even ack_configure several times
+       before its next surface commit.
+
+       A client may send multiple ack_configure requests before committing, but
+       only the last request sent before a commit indicates which configure
+       event the client really is responding to.
+      </description>
+      <arg name="serial" type="uint" summary="the serial from the configure event"/>
+    </request>
+
+    <event name="configure">
+      <description summary="suggest a surface change">
+       The configure event marks the end of a configure sequence. A configure
+       sequence is a set of one or more events configuring the state of the
+       xdg_surface, including the final xdg_surface.configure event.
+
+       Where applicable, xdg_surface surface roles will during a configure
+       sequence extend this event as a latched state sent as events before the
+       xdg_surface.configure event. Such events should be considered to make up
+       a set of atomically applied configuration states, where the
+       xdg_surface.configure commits the accumulated state.
+
+       Clients should arrange their surface for the new states, and then send
+       an ack_configure request with the serial sent in this configure event at
+       some point before committing the new surface.
+
+       If the client receives multiple configure events before it can respond
+       to one, it is free to discard all but the last event it received.
+      </description>
+      <arg name="serial" type="uint" summary="serial of the configure event"/>
+    </event>
+  </interface>
+
+  <interface name="zxdg_toplevel_v6" version="1">
+    <description summary="toplevel surface">
+      This interface defines an xdg_surface role which allows a surface to,
+      among other things, set window-like properties such as maximize,
+      fullscreen, and minimize, set application-specific metadata like title and
+      id, and well as trigger user interactive operations such as interactive
+      resize and move.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the xdg_toplevel">
+       Unmap and destroy the window. The window will be effectively
+       hidden from the user's point of view, and all state like
+       maximization, fullscreen, and so on, will be lost.
+      </description>
+    </request>
+
+    <request name="set_parent">
+      <description summary="set the parent of this surface">
+       Set the "parent" of this surface. This window should be stacked
+       above a parent. The parent surface must be mapped as long as this
+       surface is mapped.
+
+       Parent windows should be set on dialogs, toolboxes, or other
+       "auxiliary" surfaces, so that the parent is raised when the dialog
+       is raised.
+      </description>
+      <arg name="parent" type="object" interface="zxdg_toplevel_v6" allow-null="true"/>
+    </request>
+
+    <request name="set_title">
+      <description summary="set surface title">
+       Set a short title for the surface.
+
+       This string may be used to identify the surface in a task bar,
+       window list, or other user interface elements provided by the
+       compositor.
+
+       The string must be encoded in UTF-8.
+      </description>
+      <arg name="title" type="string"/>
+    </request>
+
+    <request name="set_app_id">
+      <description summary="set application ID">
+       Set an application identifier for the surface.
+
+       The app ID identifies the general class of applications to which
+       the surface belongs. The compositor can use this to group multiple
+       surfaces together, or to determine how to launch a new application.
+
+       For D-Bus activatable applications, the app ID is used as the D-Bus
+       service name.
+
+       The compositor shell will try to group application surfaces together
+       by their app ID. As a best practice, it is suggested to select app
+       ID's that match the basename of the application's .desktop file.
+       For example, "org.freedesktop.FooViewer" where the .desktop file is
+       "org.freedesktop.FooViewer.desktop".
+
+       See the desktop-entry specification [0] for more details on
+       application identifiers and how they relate to well-known D-Bus
+       names and .desktop files.
+
+       [0] http://standards.freedesktop.org/desktop-entry-spec/
+      </description>
+      <arg name="app_id" type="string"/>
+    </request>
+
+    <request name="show_window_menu">
+      <description summary="show the window menu">
+       Clients implementing client-side decorations might want to show
+       a context menu when right-clicking on the decorations, giving the
+       user a menu that they can use to maximize or minimize the window.
+
+       This request asks the compositor to pop up such a window menu at
+       the given position, relative to the local surface coordinates of
+       the parent surface. There are no guarantees as to what menu items
+       the window menu contains.
+
+       This request must be used in response to some sort of user action
+       like a button press, key press, or touch down event.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+      <arg name="serial" type="uint" summary="the serial of the user event"/>
+      <arg name="x" type="int" summary="the x position to pop up the window menu at"/>
+      <arg name="y" type="int" summary="the y position to pop up the window menu at"/>
+    </request>
+
+    <request name="move">
+      <description summary="start an interactive move">
+       Start an interactive, user-driven move of the surface.
+
+       This request must be used in response to some sort of user action
+       like a button press, key press, or touch down event. The passed
+       serial is used to determine the type of interactive move (touch,
+       pointer, etc).
+
+       The server may ignore move requests depending on the state of
+       the surface (e.g. fullscreen or maximized), or if the passed serial
+       is no longer valid.
+
+       If triggered, the surface will lose the focus of the device
+       (wl_pointer, wl_touch, etc) used for the move. It is up to the
+       compositor to visually indicate that the move is taking place, such as
+       updating a pointer cursor, during the move. There is no guarantee
+       that the device focus will return when the move is completed.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+      <arg name="serial" type="uint" summary="the serial of the user event"/>
+    </request>
+
+    <enum name="resize_edge">
+      <description summary="edge values for resizing">
+       These values are used to indicate which edge of a surface
+       is being dragged in a resize operation.
+      </description>
+      <entry name="none" value="0"/>
+      <entry name="top" value="1"/>
+      <entry name="bottom" value="2"/>
+      <entry name="left" value="4"/>
+      <entry name="top_left" value="5"/>
+      <entry name="bottom_left" value="6"/>
+      <entry name="right" value="8"/>
+      <entry name="top_right" value="9"/>
+      <entry name="bottom_right" value="10"/>
+    </enum>
+
+    <request name="resize">
+      <description summary="start an interactive resize">
+       Start a user-driven, interactive resize of the surface.
+
+       This request must be used in response to some sort of user action
+       like a button press, key press, or touch down event. The passed
+       serial is used to determine the type of interactive resize (touch,
+       pointer, etc).
+
+       The server may ignore resize requests depending on the state of
+       the surface (e.g. fullscreen or maximized).
+
+       If triggered, the client will receive configure events with the
+       "resize" state enum value and the expected sizes. See the "resize"
+       enum value for more details about what is required. The client
+       must also acknowledge configure events using "ack_configure". After
+       the resize is completed, the client will receive another "configure"
+       event without the resize state.
+
+       If triggered, the surface also will lose the focus of the device
+       (wl_pointer, wl_touch, etc) used for the resize. It is up to the
+       compositor to visually indicate that the resize is taking place,
+       such as updating a pointer cursor, during the resize. There is no
+       guarantee that the device focus will return when the resize is
+       completed.
+
+       The edges parameter specifies how the surface should be resized,
+       and is one of the values of the resize_edge enum. The compositor
+       may use this information to update the surface position for
+       example when dragging the top left corner. The compositor may also
+       use this information to adapt its behavior, e.g. choose an
+       appropriate cursor image.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+      <arg name="serial" type="uint" summary="the serial of the user event"/>
+      <arg name="edges" type="uint" summary="which edge or corner is being dragged"/>
+    </request>
+
+    <enum name="state">
+      <description summary="types of state on the surface">
+       The different state values used on the surface. This is designed for
+       state values like maximized, fullscreen. It is paired with the
+       configure event to ensure that both the client and the compositor
+       setting the state can be synchronized.
+
+       States set in this way are double-buffered. They will get applied on
+       the next commit.
+      </description>
+      <entry name="maximized" value="1" summary="the surface is maximized">
+       <description summary="the surface is maximized">
+         The surface is maximized. The window geometry specified in the configure
+         event must be obeyed by the client.
+       </description>
+      </entry>
+      <entry name="fullscreen" value="2" summary="the surface is fullscreen">
+       <description summary="the surface is fullscreen">
+         The surface is fullscreen. The window geometry specified in the configure
+         event must be obeyed by the client.
+       </description>
+      </entry>
+      <entry name="resizing" value="3" summary="the surface is being resized">
+       <description summary="the surface is being resized">
+         The surface is being resized. The window geometry specified in the
+         configure event is a maximum; the client cannot resize beyond it.
+         Clients that have aspect ratio or cell sizing configuration can use
+         a smaller size, however.
+       </description>
+      </entry>
+      <entry name="activated" value="4" summary="the surface is now activated">
+       <description summary="the surface is now activated">
+         Client window decorations should be painted as if the window is
+         active. Do not assume this means that the window actually has
+         keyboard or pointer focus.
+       </description>
+      </entry>
+    </enum>
+
+    <request name="set_max_size">
+      <description summary="set the maximum size">
+       Set a maximum size for the window.
+
+       The client can specify a maximum size so that the compositor does
+       not try to configure the window beyond this size.
+
+       The width and height arguments are in window geometry coordinates.
+       See xdg_surface.set_window_geometry.
+
+       Values set in this way are double-buffered. They will get applied
+       on the next commit.
+
+       The compositor can use this information to allow or disallow
+       different states like maximize or fullscreen and draw accurate
+       animations.
+
+       Similarly, a tiling window manager may use this information to
+       place and resize client windows in a more effective way.
+
+       The client should not rely on the compositor to obey the maximum
+       size. The compositor may decide to ignore the values set by the
+       client and request a larger size.
+
+       If never set, or a value of zero in the request, means that the
+       client has no expected maximum size in the given dimension.
+       As a result, a client wishing to reset the maximum size
+       to an unspecified state can use zero for width and height in the
+       request.
+
+       Requesting a maximum size to be smaller than the minimum size of
+       a surface is illegal and will result in a protocol error.
+
+       The width and height must be greater than or equal to zero. Using
+       strictly negative values for width and height will result in a
+       protocol error.
+      </description>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+    </request>
+
+    <request name="set_min_size">
+      <description summary="set the minimum size">
+       Set a minimum size for the window.
+
+       The client can specify a minimum size so that the compositor does
+       not try to configure the window below this size.
+
+       The width and height arguments are in window geometry coordinates.
+       See xdg_surface.set_window_geometry.
+
+       Values set in this way are double-buffered. They will get applied
+       on the next commit.
+
+       The compositor can use this information to allow or disallow
+       different states like maximize or fullscreen and draw accurate
+       animations.
+
+       Similarly, a tiling window manager may use this information to
+       place and resize client windows in a more effective way.
+
+       The client should not rely on the compositor to obey the minimum
+       size. The compositor may decide to ignore the values set by the
+       client and request a smaller size.
+
+       If never set, or a value of zero in the request, means that the
+       client has no expected minimum size in the given dimension.
+       As a result, a client wishing to reset the minimum size
+       to an unspecified state can use zero for width and height in the
+       request.
+
+       Requesting a minimum size to be larger than the maximum size of
+       a surface is illegal and will result in a protocol error.
+
+       The width and height must be greater than or equal to zero. Using
+       strictly negative values for width and height will result in a
+       protocol error.
+      </description>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+    </request>
+
+    <request name="set_maximized">
+      <description summary="maximize the window">
+       Maximize the surface.
+
+       After requesting that the surface should be maximized, the compositor
+       will respond by emitting a configure event with the "maximized" state
+       and the required window geometry. The client should then update its
+       content, drawing it in a maximized state, i.e. without shadow or other
+       decoration outside of the window geometry. The client must also
+       acknowledge the configure when committing the new content (see
+       ack_configure).
+
+       It is up to the compositor to decide how and where to maximize the
+       surface, for example which output and what region of the screen should
+       be used.
+
+       If the surface was already maximized, the compositor will still emit
+       a configure event with the "maximized" state.
+      </description>
+    </request>
+
+    <request name="unset_maximized">
+      <description summary="unmaximize the window">
+       Unmaximize the surface.
+
+       After requesting that the surface should be unmaximized, the compositor
+       will respond by emitting a configure event without the "maximized"
+       state. If available, the compositor will include the window geometry
+       dimensions the window had prior to being maximized in the configure
+       request. The client must then update its content, drawing it in a
+       regular state, i.e. potentially with shadow, etc. The client must also
+       acknowledge the configure when committing the new content (see
+       ack_configure).
+
+       It is up to the compositor to position the surface after it was
+       unmaximized; usually the position the surface had before maximizing, if
+       applicable.
+
+       If the surface was already not maximized, the compositor will still
+       emit a configure event without the "maximized" state.
+      </description>
+    </request>
+
+    <request name="set_fullscreen">
+      <description summary="set the window as fullscreen on a monitor">
+       Make the surface fullscreen.
+
+       You can specify an output that you would prefer to be fullscreen.
+       If this value is NULL, it's up to the compositor to choose which
+       display will be used to map this surface.
+
+       If the surface doesn't cover the whole output, the compositor will
+       position the surface in the center of the output and compensate with
+       black borders filling the rest of the output.
+      </description>
+      <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+    </request>
+    <request name="unset_fullscreen" />
+
+    <request name="set_minimized">
+      <description summary="set the window as minimized">
+       Request that the compositor minimize your surface. There is no
+       way to know if the surface is currently minimized, nor is there
+       any way to unset minimization on this surface.
+
+       If you are looking to throttle redrawing when minimized, please
+       instead use the wl_surface.frame event for this, as this will
+       also work with live previews on windows in Alt-Tab, Expose or
+       similar compositor features.
+      </description>
+    </request>
+
+    <event name="configure">
+      <description summary="suggest a surface change">
+       This configure event asks the client to resize its toplevel surface or
+       to change its state. The configured state should not be applied
+       immediately. See xdg_surface.configure for details.
+
+       The width and height arguments specify a hint to the window
+       about how its surface should be resized in window geometry
+       coordinates. See set_window_geometry.
+
+       If the width or height arguments are zero, it means the client
+       should decide its own window dimension. This may happen when the
+       compositor needs to configure the state of the surface but doesn't
+       have any information about any previous or expected dimension.
+
+       The states listed in the event specify how the width/height
+       arguments should be interpreted, and possibly how it should be
+       drawn.
+
+       Clients must send an ack_configure in response to this event. See
+       xdg_surface.configure and xdg_surface.ack_configure for details.
+      </description>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+      <arg name="states" type="array"/>
+    </event>
+
+    <event name="close">
+      <description summary="surface wants to be closed">
+       The close event is sent by the compositor when the user
+       wants the surface to be closed. This should be equivalent to
+       the user clicking the close button in client-side decorations,
+       if your application has any.
+
+       This is only a request that the user intends to close the
+       window. The client may choose to ignore this request, or show
+       a dialog to ask the user to save their data, etc.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="zxdg_popup_v6" version="1">
+    <description summary="short-lived, popup surfaces for menus">
+      A popup surface is a short-lived, temporary surface. It can be used to
+      implement for example menus, popovers, tooltips and other similar user
+      interface concepts.
+
+      A popup can be made to take an explicit grab. See xdg_popup.grab for
+      details.
+
+      When the popup is dismissed, a popup_done event will be sent out, and at
+      the same time the surface will be unmapped. See the xdg_popup.popup_done
+      event for details.
+
+      Explicitly destroying the xdg_popup object will also dismiss the popup and
+      unmap the surface. Clients that want to dismiss the popup when another
+      surface of their own is clicked should dismiss the popup using the destroy
+      request.
+
+      The parent surface must have either the xdg_toplevel or xdg_popup surface
+      role.
+
+      A newly created xdg_popup will be stacked on top of all previously created
+      xdg_popup surfaces associated with the same xdg_toplevel.
+
+      The parent of an xdg_popup must be mapped (see the xdg_surface
+      description) before the xdg_popup itself.
+
+      The x and y arguments passed when creating the popup object specify
+      where the top left of the popup should be placed, relative to the
+      local surface coordinates of the parent surface. See
+      xdg_surface.get_popup. An xdg_popup must intersect with or be at least
+      partially adjacent to its parent surface.
+
+      The client must call wl_surface.commit on the corresponding wl_surface
+      for the xdg_popup state to take effect.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_grab" value="0"
+            summary="tried to grab after being mapped"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="remove xdg_popup interface">
+       This destroys the popup. Explicitly destroying the xdg_popup
+       object will also dismiss the popup, and unmap the surface.
+
+       If this xdg_popup is not the "topmost" popup, a protocol error
+       will be sent.
+      </description>
+    </request>
+
+    <request name="grab">
+      <description summary="make the popup take an explicit grab">
+       This request makes the created popup take an explicit grab. An explicit
+       grab will be dismissed when the user dismisses the popup, or when the
+       client destroys the xdg_popup. This can be done by the user clicking
+       outside the surface, using the keyboard, or even locking the screen
+       through closing the lid or a timeout.
+
+       If the compositor denies the grab, the popup will be immediately
+       dismissed.
+
+       This request must be used in response to some sort of user action like a
+       button press, key press, or touch down event. The serial number of the
+       event should be passed as 'serial'.
+
+       The parent of a grabbing popup must either be an xdg_toplevel surface or
+       another xdg_popup with an explicit grab. If the parent is another
+       xdg_popup it means that the popups are nested, with this popup now being
+       the topmost popup.
+
+       Nested popups must be destroyed in the reverse order they were created
+       in, e.g. the only popup you are allowed to destroy at all times is the
+       topmost one.
+
+       When compositors choose to dismiss a popup, they may dismiss every
+       nested grabbing popup as well. When a compositor dismisses popups, it
+       will follow the same dismissing order as required from the client.
+
+       The parent of a grabbing popup must either be another xdg_popup with an
+       active explicit grab, or an xdg_popup or xdg_toplevel, if there are no
+       explicit grabs already taken.
+
+       If the topmost grabbing popup is destroyed, the grab will be returned to
+       the parent of the popup, if that parent previously had an explicit grab.
+
+       If the parent is a grabbing popup which has already been dismissed, this
+       popup will be immediately dismissed. If the parent is a popup that did
+       not take an explicit grab, an error will be raised.
+
+       During a popup grab, the client owning the grab will receive pointer
+       and touch events for all their surfaces as normal (similar to an
+       "owner-events" grab in X11 parlance), while the top most grabbing popup
+       will always have keyboard focus.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat"
+          summary="the wl_seat of the user event"/>
+      <arg name="serial" type="uint" summary="the serial of the user event"/>
+    </request>
+
+    <event name="configure">
+      <description summary="configure the popup surface">
+       This event asks the popup surface to configure itself given the
+       configuration. The configured state should not be applied immediately.
+       See xdg_surface.configure for details.
+
+       The x and y arguments represent the position the popup was placed at
+       given the xdg_positioner rule, relative to the upper left corner of the
+       window geometry of the parent surface.
+      </description>
+      <arg name="x" type="int"
+          summary="x position relative to parent surface window geometry"/>
+      <arg name="y" type="int"
+          summary="y position relative to parent surface window geometry"/>
+      <arg name="width" type="int" summary="window geometry width"/>
+      <arg name="height" type="int" summary="window geometry height"/>
+    </event>
+
+    <event name="popup_done">
+      <description summary="popup interaction is done">
+       The popup_done event is sent out when a popup is dismissed by the
+       compositor. The client should destroy the xdg_popup object at this
+       point.
+      </description>
+    </event>
+
+  </interface>
+</protocol>
diff --git a/src/protocol/xdg-shell.xml b/src/protocol/xdg-shell.xml
new file mode 100644 (file)
index 0000000..d524ea9
--- /dev/null
@@ -0,0 +1,1120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_shell">
+
+  <copyright>
+    Copyright © 2008-2013 Kristian Høgsberg
+    Copyright © 2013      Rafael Antognolli
+    Copyright © 2013      Jasper St. Pierre
+    Copyright © 2010-2013 Intel Corporation
+    Copyright © 2015-2017 Samsung Electronics Co., Ltd
+    Copyright © 2015-2017 Red Hat Inc.
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <interface name="xdg_wm_base" version="1">
+    <description summary="create desktop-style surfaces">
+      The xdg_wm_base interface is exposed as a global object enabling clients
+      to turn their wl_surfaces into windows in a desktop environment. It
+      defines the basic functionality needed for clients and the compositor to
+      create windows that can be dragged, resized, maximized, etc, as well as
+      creating transient windows such as popup menus.
+    </description>
+
+    <enum name="error">
+      <entry name="role" value="0" summary="given wl_surface has another role"/>
+      <entry name="defunct_surfaces" value="1"
+            summary="xdg_wm_base was destroyed before children"/>
+      <entry name="not_the_topmost_popup" value="2"
+            summary="the client tried to map or destroy a non-topmost popup"/>
+      <entry name="invalid_popup_parent" value="3"
+            summary="the client specified an invalid popup parent surface"/>
+      <entry name="invalid_surface_state" value="4"
+            summary="the client provided an invalid surface state"/>
+      <entry name="invalid_positioner" value="5"
+            summary="the client provided an invalid positioner"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy xdg_wm_base">
+       Destroy this xdg_wm_base object.
+
+       Destroying a bound xdg_wm_base object while there are surfaces
+       still alive created by this xdg_wm_base object instance is illegal
+       and will result in a protocol error.
+      </description>
+    </request>
+
+    <request name="create_positioner">
+      <description summary="create a positioner object">
+       Create a positioner object. A positioner object is used to position
+       surfaces relative to some parent surface. See the interface description
+       and xdg_surface.get_popup for details.
+      </description>
+      <arg name="id" type="new_id" interface="xdg_positioner"/>
+    </request>
+
+    <request name="get_xdg_surface">
+      <description summary="create a shell surface from a surface">
+       This creates an xdg_surface for the given surface. While xdg_surface
+       itself is not a role, the corresponding surface may only be assigned
+       a role extending xdg_surface, such as xdg_toplevel or xdg_popup.
+
+       This creates an xdg_surface for the given surface. An xdg_surface is
+       used as basis to define a role to a given surface, such as xdg_toplevel
+       or xdg_popup. It also manages functionality shared between xdg_surface
+       based surface roles.
+
+       See the documentation of xdg_surface for more details about what an
+       xdg_surface is and how it is used.
+      </description>
+      <arg name="id" type="new_id" interface="xdg_surface"/>
+      <arg name="surface" type="object" interface="wl_surface"/>
+    </request>
+
+    <request name="pong">
+      <description summary="respond to a ping event">
+       A client must respond to a ping event with a pong request or
+       the client may be deemed unresponsive. See xdg_wm_base.ping.
+      </description>
+      <arg name="serial" type="uint" summary="serial of the ping event"/>
+    </request>
+
+    <event name="ping">
+      <description summary="check if the client is alive">
+       The ping event asks the client if it's still alive. Pass the
+       serial specified in the event back to the compositor by sending
+       a "pong" request back with the specified serial. See xdg_wm_base.ping.
+
+       Compositors can use this to determine if the client is still
+       alive. It's unspecified what will happen if the client doesn't
+       respond to the ping request, or in what timeframe. Clients should
+       try to respond in a reasonable amount of time.
+
+       A compositor is free to ping in any way it wants, but a client must
+       always respond to any xdg_wm_base object it created.
+      </description>
+      <arg name="serial" type="uint" summary="pass this to the pong request"/>
+    </event>
+  </interface>
+
+  <interface name="xdg_positioner" version="1">
+    <description summary="child surface positioner">
+      The xdg_positioner provides a collection of rules for the placement of a
+      child surface relative to a parent surface. Rules can be defined to ensure
+      the child surface remains within the visible area's borders, and to
+      specify how the child surface changes its position, such as sliding along
+      an axis, or flipping around a rectangle. These positioner-created rules are
+      constrained by the requirement that a child surface must intersect with or
+      be at least partially adjacent to its parent surface.
+
+      See the various requests for details about possible rules.
+
+      At the time of the request, the compositor makes a copy of the rules
+      specified by the xdg_positioner. Thus, after the request is complete the
+      xdg_positioner object can be destroyed or reused; further changes to the
+      object will have no effect on previous usages.
+
+      For an xdg_positioner object to be considered complete, it must have a
+      non-zero size set by set_size, and a non-zero anchor rectangle set by
+      set_anchor_rect. Passing an incomplete xdg_positioner object when
+      positioning a surface raises an error.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_input" value="0" summary="invalid input provided"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the xdg_positioner object">
+       Notify the compositor that the xdg_positioner will no longer be used.
+      </description>
+    </request>
+
+    <request name="set_size">
+      <description summary="set the size of the to-be positioned rectangle">
+       Set the size of the surface that is to be positioned with the positioner
+       object. The size is in surface-local coordinates and corresponds to the
+       window geometry. See xdg_surface.set_window_geometry.
+
+       If a zero or negative size is set the invalid_input error is raised.
+      </description>
+      <arg name="width" type="int" summary="width of positioned rectangle"/>
+      <arg name="height" type="int" summary="height of positioned rectangle"/>
+    </request>
+
+    <request name="set_anchor_rect">
+      <description summary="set the anchor rectangle within the parent surface">
+       Specify the anchor rectangle within the parent surface that the child
+       surface will be placed relative to. The rectangle is relative to the
+       window geometry as defined by xdg_surface.set_window_geometry of the
+       parent surface.
+
+       When the xdg_positioner object is used to position a child surface, the
+       anchor rectangle may not extend outside the window geometry of the
+       positioned child's parent surface.
+
+       If a negative size is set the invalid_input error is raised.
+      </description>
+      <arg name="x" type="int" summary="x position of anchor rectangle"/>
+      <arg name="y" type="int" summary="y position of anchor rectangle"/>
+      <arg name="width" type="int" summary="width of anchor rectangle"/>
+      <arg name="height" type="int" summary="height of anchor rectangle"/>
+    </request>
+
+    <enum name="anchor">
+      <entry name="none" value="0"/>
+      <entry name="top" value="1"/>
+      <entry name="bottom" value="2"/>
+      <entry name="left" value="3"/>
+      <entry name="right" value="4"/>
+      <entry name="top_left" value="5"/>
+      <entry name="bottom_left" value="6"/>
+      <entry name="top_right" value="7"/>
+      <entry name="bottom_right" value="8"/>
+    </enum>
+
+    <request name="set_anchor">
+      <description summary="set anchor rectangle anchor">
+       Defines the anchor point for the anchor rectangle. The specified anchor
+       is used derive an anchor point that the child surface will be
+       positioned relative to. If a corner anchor is set (e.g. 'top_left' or
+       'bottom_right'), the anchor point will be at the specified corner;
+       otherwise, the derived anchor point will be centered on the specified
+       edge, or in the center of the anchor rectangle if no edge is specified.
+      </description>
+      <arg name="anchor" type="uint" enum="anchor"
+          summary="anchor"/>
+    </request>
+
+    <enum name="gravity">
+      <entry name="none" value="0"/>
+      <entry name="top" value="1"/>
+      <entry name="bottom" value="2"/>
+      <entry name="left" value="3"/>
+      <entry name="right" value="4"/>
+      <entry name="top_left" value="5"/>
+      <entry name="bottom_left" value="6"/>
+      <entry name="top_right" value="7"/>
+      <entry name="bottom_right" value="8"/>
+    </enum>
+
+    <request name="set_gravity">
+      <description summary="set child surface gravity">
+       Defines in what direction a surface should be positioned, relative to
+       the anchor point of the parent surface. If a corner gravity is
+       specified (e.g. 'bottom_right' or 'top_left'), then the child surface
+       will be placed towards the specified gravity; otherwise, the child
+       surface will be centered over the anchor point on any axis that had no
+       gravity specified.
+      </description>
+      <arg name="gravity" type="uint" enum="gravity"
+          summary="gravity direction"/>
+    </request>
+
+    <enum name="constraint_adjustment" bitfield="true">
+      <description summary="constraint adjustments">
+       The constraint adjustment value define ways the compositor will adjust
+       the position of the surface, if the unadjusted position would result
+       in the surface being partly constrained.
+
+       Whether a surface is considered 'constrained' is left to the compositor
+       to determine. For example, the surface may be partly outside the
+       compositor's defined 'work area', thus necessitating the child surface's
+       position be adjusted until it is entirely inside the work area.
+
+       The adjustments can be combined, according to a defined precedence: 1)
+       Flip, 2) Slide, 3) Resize.
+      </description>
+      <entry name="none" value="0">
+       <description summary="don't move the child surface when constrained">
+         Don't alter the surface position even if it is constrained on some
+         axis, for example partially outside the edge of an output.
+       </description>
+      </entry>
+      <entry name="slide_x" value="1">
+       <description summary="move along the x axis until unconstrained">
+         Slide the surface along the x axis until it is no longer constrained.
+
+         First try to slide towards the direction of the gravity on the x axis
+         until either the edge in the opposite direction of the gravity is
+         unconstrained or the edge in the direction of the gravity is
+         constrained.
+
+         Then try to slide towards the opposite direction of the gravity on the
+         x axis until either the edge in the direction of the gravity is
+         unconstrained or the edge in the opposite direction of the gravity is
+         constrained.
+       </description>
+      </entry>
+      <entry name="slide_y" value="2">
+       <description summary="move along the y axis until unconstrained">
+         Slide the surface along the y axis until it is no longer constrained.
+
+         First try to slide towards the direction of the gravity on the y axis
+         until either the edge in the opposite direction of the gravity is
+         unconstrained or the edge in the direction of the gravity is
+         constrained.
+
+         Then try to slide towards the opposite direction of the gravity on the
+         y axis until either the edge in the direction of the gravity is
+         unconstrained or the edge in the opposite direction of the gravity is
+         constrained.
+       </description>
+      </entry>
+      <entry name="flip_x" value="4">
+       <description summary="invert the anchor and gravity on the x axis">
+         Invert the anchor and gravity on the x axis if the surface is
+         constrained on the x axis. For example, if the left edge of the
+         surface is constrained, the gravity is 'left' and the anchor is
+         'left', change the gravity to 'right' and the anchor to 'right'.
+
+         If the adjusted position also ends up being constrained, the resulting
+         position of the flip_x adjustment will be the one before the
+         adjustment.
+       </description>
+      </entry>
+      <entry name="flip_y" value="8">
+       <description summary="invert the anchor and gravity on the y axis">
+         Invert the anchor and gravity on the y axis if the surface is
+         constrained on the y axis. For example, if the bottom edge of the
+         surface is constrained, the gravity is 'bottom' and the anchor is
+         'bottom', change the gravity to 'top' and the anchor to 'top'.
+
+         The adjusted position is calculated given the original anchor
+         rectangle and offset, but with the new flipped anchor and gravity
+         values.
+
+         If the adjusted position also ends up being constrained, the resulting
+         position of the flip_y adjustment will be the one before the
+         adjustment.
+       </description>
+      </entry>
+      <entry name="resize_x" value="16">
+       <description summary="horizontally resize the surface">
+         Resize the surface horizontally so that it is completely
+         unconstrained.
+       </description>
+      </entry>
+      <entry name="resize_y" value="32">
+       <description summary="vertically resize the surface">
+         Resize the surface vertically so that it is completely unconstrained.
+       </description>
+      </entry>
+    </enum>
+
+    <request name="set_constraint_adjustment">
+      <description summary="set the adjustment to be done when constrained">
+       Specify how the window should be positioned if the originally intended
+       position caused the surface to be constrained, meaning at least
+       partially outside positioning boundaries set by the compositor. The
+       adjustment is set by constructing a bitmask describing the adjustment to
+       be made when the surface is constrained on that axis.
+
+       If no bit for one axis is set, the compositor will assume that the child
+       surface should not change its position on that axis when constrained.
+
+       If more than one bit for one axis is set, the order of how adjustments
+       are applied is specified in the corresponding adjustment descriptions.
+
+       The default adjustment is none.
+      </description>
+      <arg name="constraint_adjustment" type="uint"
+          summary="bit mask of constraint adjustments"/>
+    </request>
+
+    <request name="set_offset">
+      <description summary="set surface position offset">
+       Specify the surface position offset relative to the position of the
+       anchor on the anchor rectangle and the anchor on the surface. For
+       example if the anchor of the anchor rectangle is at (x, y), the surface
+       has the gravity bottom|right, and the offset is (ox, oy), the calculated
+       surface position will be (x + ox, y + oy). The offset position of the
+       surface is the one used for constraint testing. See
+       set_constraint_adjustment.
+
+       An example use case is placing a popup menu on top of a user interface
+       element, while aligning the user interface element of the parent surface
+       with some user interface element placed somewhere in the popup surface.
+      </description>
+      <arg name="x" type="int" summary="surface position x offset"/>
+      <arg name="y" type="int" summary="surface position y offset"/>
+    </request>
+  </interface>
+
+  <interface name="xdg_surface" version="1">
+    <description summary="desktop user interface surface base interface">
+      An interface that may be implemented by a wl_surface, for
+      implementations that provide a desktop-style user interface.
+
+      It provides a base set of functionality required to construct user
+      interface elements requiring management by the compositor, such as
+      toplevel windows, menus, etc. The types of functionality are split into
+      xdg_surface roles.
+
+      Creating an xdg_surface does not set the role for a wl_surface. In order
+      to map an xdg_surface, the client must create a role-specific object
+      using, e.g., get_toplevel, get_popup. The wl_surface for any given
+      xdg_surface can have at most one role, and may not be assigned any role
+      not based on xdg_surface.
+
+      A role must be assigned before any other requests are made to the
+      xdg_surface object.
+
+      The client must call wl_surface.commit on the corresponding wl_surface
+      for the xdg_surface state to take effect.
+
+      Creating an xdg_surface from a wl_surface which has a buffer attached or
+      committed is a client error, and any attempts by a client to attach or
+      manipulate a buffer prior to the first xdg_surface.configure call must
+      also be treated as errors.
+
+      Mapping an xdg_surface-based role surface is defined as making it
+      possible for the surface to be shown by the compositor. Note that
+      a mapped surface is not guaranteed to be visible once it is mapped.
+
+      For an xdg_surface to be mapped by the compositor, the following
+      conditions must be met:
+      (1) the client has assigned an xdg_surface-based role to the surface
+      (2) the client has set and committed the xdg_surface state and the
+         role-dependent state to the surface
+      (3) the client has committed a buffer to the surface
+
+      A newly-unmapped surface is considered to have met condition (1) out
+      of the 3 required conditions for mapping a surface if its role surface
+      has not been destroyed.
+    </description>
+
+    <enum name="error">
+      <entry name="not_constructed" value="1"/>
+      <entry name="already_constructed" value="2"/>
+      <entry name="unconfigured_buffer" value="3"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the xdg_surface">
+       Destroy the xdg_surface object. An xdg_surface must only be destroyed
+       after its role object has been destroyed.
+      </description>
+    </request>
+
+    <request name="get_toplevel">
+      <description summary="assign the xdg_toplevel surface role">
+       This creates an xdg_toplevel object for the given xdg_surface and gives
+       the associated wl_surface the xdg_toplevel role.
+
+       See the documentation of xdg_toplevel for more details about what an
+       xdg_toplevel is and how it is used.
+      </description>
+      <arg name="id" type="new_id" interface="xdg_toplevel"/>
+    </request>
+
+    <request name="get_popup">
+      <description summary="assign the xdg_popup surface role">
+       This creates an xdg_popup object for the given xdg_surface and gives
+       the associated wl_surface the xdg_popup role.
+
+       If null is passed as a parent, a parent surface must be specified using
+       some other protocol, before committing the initial state.
+
+       See the documentation of xdg_popup for more details about what an
+       xdg_popup is and how it is used.
+      </description>
+      <arg name="id" type="new_id" interface="xdg_popup"/>
+      <arg name="parent" type="object" interface="xdg_surface" allow-null="true"/>
+      <arg name="positioner" type="object" interface="xdg_positioner"/>
+    </request>
+
+    <request name="set_window_geometry">
+      <description summary="set the new window geometry">
+       The window geometry of a surface is its "visible bounds" from the
+       user's perspective. Client-side decorations often have invisible
+       portions like drop-shadows which should be ignored for the
+       purposes of aligning, placing and constraining windows.
+
+       The window geometry is double buffered, and will be applied at the
+       time wl_surface.commit of the corresponding wl_surface is called.
+
+       When maintaining a position, the compositor should treat the (x, y)
+       coordinate of the window geometry as the top left corner of the window.
+       A client changing the (x, y) window geometry coordinate should in
+       general not alter the position of the window.
+
+       Once the window geometry of the surface is set, it is not possible to
+       unset it, and it will remain the same until set_window_geometry is
+       called again, even if a new subsurface or buffer is attached.
+
+       If never set, the value is the full bounds of the surface,
+       including any subsurfaces. This updates dynamically on every
+       commit. This unset is meant for extremely simple clients.
+
+       The arguments are given in the surface-local coordinate space of
+       the wl_surface associated with this xdg_surface.
+
+       The width and height must be greater than zero. Setting an invalid size
+       will raise an error. When applied, the effective window geometry will be
+       the set window geometry clamped to the bounding rectangle of the
+       combined geometry of the surface of the xdg_surface and the associated
+       subsurfaces.
+      </description>
+      <arg name="x" type="int"/>
+      <arg name="y" type="int"/>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+    </request>
+
+    <request name="ack_configure">
+      <description summary="ack a configure event">
+       When a configure event is received, if a client commits the
+       surface in response to the configure event, then the client
+       must make an ack_configure request sometime before the commit
+       request, passing along the serial of the configure event.
+
+       For instance, for toplevel surfaces the compositor might use this
+       information to move a surface to the top left only when the client has
+       drawn itself for the maximized or fullscreen state.
+
+       If the client receives multiple configure events before it
+       can respond to one, it only has to ack the last configure event.
+
+       A client is not required to commit immediately after sending
+       an ack_configure request - it may even ack_configure several times
+       before its next surface commit.
+
+       A client may send multiple ack_configure requests before committing, but
+       only the last request sent before a commit indicates which configure
+       event the client really is responding to.
+      </description>
+      <arg name="serial" type="uint" summary="the serial from the configure event"/>
+    </request>
+
+    <event name="configure">
+      <description summary="suggest a surface change">
+       The configure event marks the end of a configure sequence. A configure
+       sequence is a set of one or more events configuring the state of the
+       xdg_surface, including the final xdg_surface.configure event.
+
+       Where applicable, xdg_surface surface roles will during a configure
+       sequence extend this event as a latched state sent as events before the
+       xdg_surface.configure event. Such events should be considered to make up
+       a set of atomically applied configuration states, where the
+       xdg_surface.configure commits the accumulated state.
+
+       Clients should arrange their surface for the new states, and then send
+       an ack_configure request with the serial sent in this configure event at
+       some point before committing the new surface.
+
+       If the client receives multiple configure events before it can respond
+       to one, it is free to discard all but the last event it received.
+      </description>
+      <arg name="serial" type="uint" summary="serial of the configure event"/>
+    </event>
+  </interface>
+
+  <interface name="xdg_toplevel" version="1">
+    <description summary="toplevel surface">
+      This interface defines an xdg_surface role which allows a surface to,
+      among other things, set window-like properties such as maximize,
+      fullscreen, and minimize, set application-specific metadata like title and
+      id, and well as trigger user interactive operations such as interactive
+      resize and move.
+
+      Unmapping an xdg_toplevel means that the surface cannot be shown
+      by the compositor until it is explicitly mapped again.
+      All active operations (e.g., move, resize) are canceled and all
+      attributes (e.g. title, state, stacking, ...) are discarded for
+      an xdg_toplevel surface when it is unmapped.
+
+      Attaching a null buffer to a toplevel unmaps the surface.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the xdg_toplevel">
+       This request destroys the role surface and unmaps the surface;
+       see "Unmapping" behavior in interface section for details.
+      </description>
+    </request>
+
+    <request name="set_parent">
+      <description summary="set the parent of this surface">
+       Set the "parent" of this surface. This surface should be stacked
+       above the parent surface and all other ancestor surfaces.
+
+       Parent windows should be set on dialogs, toolboxes, or other
+       "auxiliary" surfaces, so that the parent is raised when the dialog
+       is raised.
+
+       Setting a null parent for a child window removes any parent-child
+       relationship for the child. Setting a null parent for a window which
+       currently has no parent is a no-op.
+
+       If the parent is unmapped then its children are managed as
+       though the parent of the now-unmapped parent has become the
+       parent of this surface. If no parent exists for the now-unmapped
+       parent then the children are managed as though they have no
+       parent surface.
+      </description>
+      <arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/>
+    </request>
+
+    <request name="set_title">
+      <description summary="set surface title">
+       Set a short title for the surface.
+
+       This string may be used to identify the surface in a task bar,
+       window list, or other user interface elements provided by the
+       compositor.
+
+       The string must be encoded in UTF-8.
+      </description>
+      <arg name="title" type="string"/>
+    </request>
+
+    <request name="set_app_id">
+      <description summary="set application ID">
+       Set an application identifier for the surface.
+
+       The app ID identifies the general class of applications to which
+       the surface belongs. The compositor can use this to group multiple
+       surfaces together, or to determine how to launch a new application.
+
+       For D-Bus activatable applications, the app ID is used as the D-Bus
+       service name.
+
+       The compositor shell will try to group application surfaces together
+       by their app ID. As a best practice, it is suggested to select app
+       ID's that match the basename of the application's .desktop file.
+       For example, "org.freedesktop.FooViewer" where the .desktop file is
+       "org.freedesktop.FooViewer.desktop".
+
+       See the desktop-entry specification [0] for more details on
+       application identifiers and how they relate to well-known D-Bus
+       names and .desktop files.
+
+       [0] http://standards.freedesktop.org/desktop-entry-spec/
+      </description>
+      <arg name="app_id" type="string"/>
+    </request>
+
+    <request name="show_window_menu">
+      <description summary="show the window menu">
+       Clients implementing client-side decorations might want to show
+       a context menu when right-clicking on the decorations, giving the
+       user a menu that they can use to maximize or minimize the window.
+
+       This request asks the compositor to pop up such a window menu at
+       the given position, relative to the local surface coordinates of
+       the parent surface. There are no guarantees as to what menu items
+       the window menu contains.
+
+       This request must be used in response to some sort of user action
+       like a button press, key press, or touch down event.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+      <arg name="serial" type="uint" summary="the serial of the user event"/>
+      <arg name="x" type="int" summary="the x position to pop up the window menu at"/>
+      <arg name="y" type="int" summary="the y position to pop up the window menu at"/>
+    </request>
+
+    <request name="move">
+      <description summary="start an interactive move">
+       Start an interactive, user-driven move of the surface.
+
+       This request must be used in response to some sort of user action
+       like a button press, key press, or touch down event. The passed
+       serial is used to determine the type of interactive move (touch,
+       pointer, etc).
+
+       The server may ignore move requests depending on the state of
+       the surface (e.g. fullscreen or maximized), or if the passed serial
+       is no longer valid.
+
+       If triggered, the surface will lose the focus of the device
+       (wl_pointer, wl_touch, etc) used for the move. It is up to the
+       compositor to visually indicate that the move is taking place, such as
+       updating a pointer cursor, during the move. There is no guarantee
+       that the device focus will return when the move is completed.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+      <arg name="serial" type="uint" summary="the serial of the user event"/>
+    </request>
+
+    <enum name="resize_edge">
+      <description summary="edge values for resizing">
+       These values are used to indicate which edge of a surface
+       is being dragged in a resize operation.
+      </description>
+      <entry name="none" value="0"/>
+      <entry name="top" value="1"/>
+      <entry name="bottom" value="2"/>
+      <entry name="left" value="4"/>
+      <entry name="top_left" value="5"/>
+      <entry name="bottom_left" value="6"/>
+      <entry name="right" value="8"/>
+      <entry name="top_right" value="9"/>
+      <entry name="bottom_right" value="10"/>
+    </enum>
+
+    <request name="resize">
+      <description summary="start an interactive resize">
+       Start a user-driven, interactive resize of the surface.
+
+       This request must be used in response to some sort of user action
+       like a button press, key press, or touch down event. The passed
+       serial is used to determine the type of interactive resize (touch,
+       pointer, etc).
+
+       The server may ignore resize requests depending on the state of
+       the surface (e.g. fullscreen or maximized).
+
+       If triggered, the client will receive configure events with the
+       "resize" state enum value and the expected sizes. See the "resize"
+       enum value for more details about what is required. The client
+       must also acknowledge configure events using "ack_configure". After
+       the resize is completed, the client will receive another "configure"
+       event without the resize state.
+
+       If triggered, the surface also will lose the focus of the device
+       (wl_pointer, wl_touch, etc) used for the resize. It is up to the
+       compositor to visually indicate that the resize is taking place,
+       such as updating a pointer cursor, during the resize. There is no
+       guarantee that the device focus will return when the resize is
+       completed.
+
+       The edges parameter specifies how the surface should be resized,
+       and is one of the values of the resize_edge enum. The compositor
+       may use this information to update the surface position for
+       example when dragging the top left corner. The compositor may also
+       use this information to adapt its behavior, e.g. choose an
+       appropriate cursor image.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+      <arg name="serial" type="uint" summary="the serial of the user event"/>
+      <arg name="edges" type="uint" summary="which edge or corner is being dragged"/>
+    </request>
+
+    <enum name="state">
+      <description summary="types of state on the surface">
+       The different state values used on the surface. This is designed for
+       state values like maximized, fullscreen. It is paired with the
+       configure event to ensure that both the client and the compositor
+       setting the state can be synchronized.
+
+       States set in this way are double-buffered. They will get applied on
+       the next commit.
+      </description>
+      <entry name="maximized" value="1" summary="the surface is maximized">
+       <description summary="the surface is maximized">
+         The surface is maximized. The window geometry specified in the configure
+         event must be obeyed by the client.
+       </description>
+      </entry>
+      <entry name="fullscreen" value="2" summary="the surface is fullscreen">
+       <description summary="the surface is fullscreen">
+         The surface is fullscreen. The window geometry specified in the
+         configure event is a maximum; the client cannot resize beyond it. For
+         a surface to cover the whole fullscreened area, the geometry
+         dimensions must be obeyed by the client. For more details, see
+         xdg_toplevel.set_fullscreen.
+       </description>
+      </entry>
+      <entry name="resizing" value="3" summary="the surface is being resized">
+       <description summary="the surface is being resized">
+         The surface is being resized. The window geometry specified in the
+         configure event is a maximum; the client cannot resize beyond it.
+         Clients that have aspect ratio or cell sizing configuration can use
+         a smaller size, however.
+       </description>
+      </entry>
+      <entry name="activated" value="4" summary="the surface is now activated">
+       <description summary="the surface is now activated">
+         Client window decorations should be painted as if the window is
+         active. Do not assume this means that the window actually has
+         keyboard or pointer focus.
+       </description>
+      </entry>
+    </enum>
+
+    <request name="set_max_size">
+      <description summary="set the maximum size">
+       Set a maximum size for the window.
+
+       The client can specify a maximum size so that the compositor does
+       not try to configure the window beyond this size.
+
+       The width and height arguments are in window geometry coordinates.
+       See xdg_surface.set_window_geometry.
+
+       Values set in this way are double-buffered. They will get applied
+       on the next commit.
+
+       The compositor can use this information to allow or disallow
+       different states like maximize or fullscreen and draw accurate
+       animations.
+
+       Similarly, a tiling window manager may use this information to
+       place and resize client windows in a more effective way.
+
+       The client should not rely on the compositor to obey the maximum
+       size. The compositor may decide to ignore the values set by the
+       client and request a larger size.
+
+       If never set, or a value of zero in the request, means that the
+       client has no expected maximum size in the given dimension.
+       As a result, a client wishing to reset the maximum size
+       to an unspecified state can use zero for width and height in the
+       request.
+
+       Requesting a maximum size to be smaller than the minimum size of
+       a surface is illegal and will result in a protocol error.
+
+       The width and height must be greater than or equal to zero. Using
+       strictly negative values for width and height will result in a
+       protocol error.
+      </description>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+    </request>
+
+    <request name="set_min_size">
+      <description summary="set the minimum size">
+       Set a minimum size for the window.
+
+       The client can specify a minimum size so that the compositor does
+       not try to configure the window below this size.
+
+       The width and height arguments are in window geometry coordinates.
+       See xdg_surface.set_window_geometry.
+
+       Values set in this way are double-buffered. They will get applied
+       on the next commit.
+
+       The compositor can use this information to allow or disallow
+       different states like maximize or fullscreen and draw accurate
+       animations.
+
+       Similarly, a tiling window manager may use this information to
+       place and resize client windows in a more effective way.
+
+       The client should not rely on the compositor to obey the minimum
+       size. The compositor may decide to ignore the values set by the
+       client and request a smaller size.
+
+       If never set, or a value of zero in the request, means that the
+       client has no expected minimum size in the given dimension.
+       As a result, a client wishing to reset the minimum size
+       to an unspecified state can use zero for width and height in the
+       request.
+
+       Requesting a minimum size to be larger than the maximum size of
+       a surface is illegal and will result in a protocol error.
+
+       The width and height must be greater than or equal to zero. Using
+       strictly negative values for width and height will result in a
+       protocol error.
+      </description>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+    </request>
+
+    <request name="set_maximized">
+      <description summary="maximize the window">
+       Maximize the surface.
+
+       After requesting that the surface should be maximized, the compositor
+       will respond by emitting a configure event with the "maximized" state
+       and the required window geometry. The client should then update its
+       content, drawing it in a maximized state, i.e. without shadow or other
+       decoration outside of the window geometry. The client must also
+       acknowledge the configure when committing the new content (see
+       ack_configure).
+
+       It is up to the compositor to decide how and where to maximize the
+       surface, for example which output and what region of the screen should
+       be used.
+
+       If the surface was already maximized, the compositor will still emit
+       a configure event with the "maximized" state.
+
+       If the surface is in a fullscreen state, this request has no direct
+       effect. It will alter the state the surface is returned to when
+       unmaximized if not overridden by the compositor.
+      </description>
+    </request>
+
+    <request name="unset_maximized">
+      <description summary="unmaximize the window">
+       Unmaximize the surface.
+
+       After requesting that the surface should be unmaximized, the compositor
+       will respond by emitting a configure event without the "maximized"
+       state. If available, the compositor will include the window geometry
+       dimensions the window had prior to being maximized in the configure
+       event. The client must then update its content, drawing it in a
+       regular state, i.e. potentially with shadow, etc. The client must also
+       acknowledge the configure when committing the new content (see
+       ack_configure).
+
+       It is up to the compositor to position the surface after it was
+       unmaximized; usually the position the surface had before maximizing, if
+       applicable.
+
+       If the surface was already not maximized, the compositor will still
+       emit a configure event without the "maximized" state.
+
+       If the surface is in a fullscreen state, this request has no direct
+       effect. It will alter the state the surface is returned to when
+       unmaximized if not overridden by the compositor.
+      </description>
+    </request>
+
+    <request name="set_fullscreen">
+      <description summary="set the window as fullscreen on an output">
+       Make the surface fullscreen.
+
+       After requesting that the surface should be fullscreened, the
+       compositor will respond by emitting a configure event with the
+       "fullscreen" state and the fullscreen window geometry. The client must
+       also acknowledge the configure when committing the new content (see
+       ack_configure).
+
+       The output passed by the request indicates the client's preference as
+       to which display it should be set fullscreen on. If this value is NULL,
+       it's up to the compositor to choose which display will be used to map
+       this surface.
+
+       If the surface doesn't cover the whole output, the compositor will
+       position the surface in the center of the output and compensate with
+       with border fill covering the rest of the output. The content of the
+       border fill is undefined, but should be assumed to be in some way that
+       attempts to blend into the surrounding area (e.g. solid black).
+
+       If the fullscreened surface is not opaque, the compositor must make
+       sure that other screen content not part of the same surface tree (made
+       up of subsurfaces, popups or similarly coupled surfaces) are not
+       visible below the fullscreened surface.
+      </description>
+      <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+    </request>
+
+    <request name="unset_fullscreen">
+      <description summary="unset the window as fullscreen">
+       Make the surface no longer fullscreen.
+
+       After requesting that the surface should be unfullscreened, the
+       compositor will respond by emitting a configure event without the
+       "fullscreen" state.
+
+       Making a surface unfullscreen sets states for the surface based on the following:
+       * the state(s) it may have had before becoming fullscreen
+       * any state(s) decided by the compositor
+       * any state(s) requested by the client while the surface was fullscreen
+
+       The compositor may include the previous window geometry dimensions in
+       the configure event, if applicable.
+
+       The client must also acknowledge the configure when committing the new
+       content (see ack_configure).
+      </description>
+    </request>
+
+    <request name="set_minimized">
+      <description summary="set the window as minimized">
+       Request that the compositor minimize your surface. There is no
+       way to know if the surface is currently minimized, nor is there
+       any way to unset minimization on this surface.
+
+       If you are looking to throttle redrawing when minimized, please
+       instead use the wl_surface.frame event for this, as this will
+       also work with live previews on windows in Alt-Tab, Expose or
+       similar compositor features.
+      </description>
+    </request>
+
+    <event name="configure">
+      <description summary="suggest a surface change">
+       This configure event asks the client to resize its toplevel surface or
+       to change its state. The configured state should not be applied
+       immediately. See xdg_surface.configure for details.
+
+       The width and height arguments specify a hint to the window
+       about how its surface should be resized in window geometry
+       coordinates. See set_window_geometry.
+
+       If the width or height arguments are zero, it means the client
+       should decide its own window dimension. This may happen when the
+       compositor needs to configure the state of the surface but doesn't
+       have any information about any previous or expected dimension.
+
+       The states listed in the event specify how the width/height
+       arguments should be interpreted, and possibly how it should be
+       drawn.
+
+       Clients must send an ack_configure in response to this event. See
+       xdg_surface.configure and xdg_surface.ack_configure for details.
+      </description>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+      <arg name="states" type="array"/>
+    </event>
+
+    <event name="close">
+      <description summary="surface wants to be closed">
+       The close event is sent by the compositor when the user
+       wants the surface to be closed. This should be equivalent to
+       the user clicking the close button in client-side decorations,
+       if your application has any.
+
+       This is only a request that the user intends to close the
+       window. The client may choose to ignore this request, or show
+       a dialog to ask the user to save their data, etc.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="xdg_popup" version="1">
+    <description summary="short-lived, popup surfaces for menus">
+      A popup surface is a short-lived, temporary surface. It can be used to
+      implement for example menus, popovers, tooltips and other similar user
+      interface concepts.
+
+      A popup can be made to take an explicit grab. See xdg_popup.grab for
+      details.
+
+      When the popup is dismissed, a popup_done event will be sent out, and at
+      the same time the surface will be unmapped. See the xdg_popup.popup_done
+      event for details.
+
+      Explicitly destroying the xdg_popup object will also dismiss the popup and
+      unmap the surface. Clients that want to dismiss the popup when another
+      surface of their own is clicked should dismiss the popup using the destroy
+      request.
+
+      The parent surface must have either the xdg_toplevel or xdg_popup surface
+      role.
+
+      A newly created xdg_popup will be stacked on top of all previously created
+      xdg_popup surfaces associated with the same xdg_toplevel.
+
+      The parent of an xdg_popup must be mapped (see the xdg_surface
+      description) before the xdg_popup itself.
+
+      The x and y arguments passed when creating the popup object specify
+      where the top left of the popup should be placed, relative to the
+      local surface coordinates of the parent surface. See
+      xdg_surface.get_popup. An xdg_popup must intersect with or be at least
+      partially adjacent to its parent surface.
+
+      The client must call wl_surface.commit on the corresponding wl_surface
+      for the xdg_popup state to take effect.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_grab" value="0"
+            summary="tried to grab after being mapped"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="remove xdg_popup interface">
+       This destroys the popup. Explicitly destroying the xdg_popup
+       object will also dismiss the popup, and unmap the surface.
+
+       If this xdg_popup is not the "topmost" popup, a protocol error
+       will be sent.
+      </description>
+    </request>
+
+    <request name="grab">
+      <description summary="make the popup take an explicit grab">
+       This request makes the created popup take an explicit grab. An explicit
+       grab will be dismissed when the user dismisses the popup, or when the
+       client destroys the xdg_popup. This can be done by the user clicking
+       outside the surface, using the keyboard, or even locking the screen
+       through closing the lid or a timeout.
+
+       If the compositor denies the grab, the popup will be immediately
+       dismissed.
+
+       This request must be used in response to some sort of user action like a
+       button press, key press, or touch down event. The serial number of the
+       event should be passed as 'serial'.
+
+       The parent of a grabbing popup must either be an xdg_toplevel surface or
+       another xdg_popup with an explicit grab. If the parent is another
+       xdg_popup it means that the popups are nested, with this popup now being
+       the topmost popup.
+
+       Nested popups must be destroyed in the reverse order they were created
+       in, e.g. the only popup you are allowed to destroy at all times is the
+       topmost one.
+
+       When compositors choose to dismiss a popup, they may dismiss every
+       nested grabbing popup as well. When a compositor dismisses popups, it
+       will follow the same dismissing order as required from the client.
+
+       The parent of a grabbing popup must either be another xdg_popup with an
+       active explicit grab, or an xdg_popup or xdg_toplevel, if there are no
+       explicit grabs already taken.
+
+       If the topmost grabbing popup is destroyed, the grab will be returned to
+       the parent of the popup, if that parent previously had an explicit grab.
+
+       If the parent is a grabbing popup which has already been dismissed, this
+       popup will be immediately dismissed. If the parent is a popup that did
+       not take an explicit grab, an error will be raised.
+
+       During a popup grab, the client owning the grab will receive pointer
+       and touch events for all their surfaces as normal (similar to an
+       "owner-events" grab in X11 parlance), while the top most grabbing popup
+       will always have keyboard focus.
+      </description>
+      <arg name="seat" type="object" interface="wl_seat"
+          summary="the wl_seat of the user event"/>
+      <arg name="serial" type="uint" summary="the serial of the user event"/>
+    </request>
+
+    <event name="configure">
+      <description summary="configure the popup surface">
+       This event asks the popup surface to configure itself given the
+       configuration. The configured state should not be applied immediately.
+       See xdg_surface.configure for details.
+
+       The x and y arguments represent the position the popup was placed at
+       given the xdg_positioner rule, relative to the upper left corner of the
+       window geometry of the parent surface.
+      </description>
+      <arg name="x" type="int"
+          summary="x position relative to parent surface window geometry"/>
+      <arg name="y" type="int"
+          summary="y position relative to parent surface window geometry"/>
+      <arg name="width" type="int" summary="window geometry width"/>
+      <arg name="height" type="int" summary="window geometry height"/>
+    </event>
+
+    <event name="popup_done">
+      <description summary="popup interaction is done">
+       The popup_done event is sent out when a popup is dismissed by the
+       compositor. The client should destroy the xdg_popup object at this
+       point.
+      </description>
+    </event>
+
+  </interface>
+</protocol>
diff --git a/src/relative_pointer_unstable_v1.cpp b/src/relative_pointer_unstable_v1.cpp
new file mode 100644 (file)
index 0000000..deb438c
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "relative_pointer_unstable_v1.h"
+
+#include "in_process_server.h"
+#include <version_specifier.h>
+
+wlcs::ZwpRelativePointerManagerV1::ZwpRelativePointerManagerV1(Client& client) :
+    manager{client.bind_if_supported<zwp_relative_pointer_manager_v1>(AnyVersion)}
+{
+
+}
+
+wlcs::ZwpRelativePointerManagerV1::~ZwpRelativePointerManagerV1() = default;
+
+wlcs::ZwpRelativePointerManagerV1::operator zwp_relative_pointer_manager_v1*() const
+{
+    return manager;
+}
+
+wlcs::ZwpRelativePointerV1::ZwpRelativePointerV1(wlcs::ZwpRelativePointerManagerV1& manager, wl_pointer* pointer) :
+    relative_pointer{zwp_relative_pointer_manager_v1_get_relative_pointer(manager, pointer)},
+    version{zwp_relative_pointer_v1_get_version(relative_pointer)}
+{
+    zwp_relative_pointer_v1_set_user_data(relative_pointer, this);
+    zwp_relative_pointer_v1_add_listener(relative_pointer, &listener, this);
+}
+
+wlcs::ZwpRelativePointerV1::~ZwpRelativePointerV1()
+{
+    zwp_relative_pointer_v1_destroy(relative_pointer);
+}
+
+wlcs::ZwpRelativePointerV1::operator zwp_relative_pointer_v1*() const
+{
+    return relative_pointer;
+}
+
+zwp_relative_pointer_v1_listener const wlcs::ZwpRelativePointerV1::listener =
+    {
+        [](void* self, auto*, auto... args) { static_cast<ZwpRelativePointerV1*>(self)->relative_motion(args...); }
+    };
diff --git a/src/shared_library.cpp b/src/shared_library.cpp
new file mode 100644 (file)
index 0000000..8fa7e26
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2013 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2 or 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "shared_library.h"
+
+#include <boost/throw_exception.hpp>
+#include <boost/exception/info.hpp>
+
+#include <dlfcn.h>
+
+#include <stdexcept>
+
+wlcs::SharedLibrary::SharedLibrary(char const* library_name) :
+    so(dlopen(library_name, RTLD_NOW | RTLD_LOCAL))
+{
+    if (!so)
+    {
+        BOOST_THROW_EXCEPTION(std::runtime_error(dlerror()));
+    }
+}
+
+wlcs::SharedLibrary::SharedLibrary(std::string const& library_name) :
+    SharedLibrary(library_name.c_str()) {}
+
+wlcs::SharedLibrary::~SharedLibrary() noexcept
+{
+    dlclose(so);
+}
+
+void* wlcs::SharedLibrary::load_symbol(char const* function_name) const
+{
+    if (void* result = dlsym(so, function_name))
+    {
+        return result;
+    }
+    else
+    {
+        BOOST_THROW_EXCEPTION(std::runtime_error(dlerror()));
+    }
+}
diff --git a/src/surface_builder.cpp b/src/surface_builder.cpp
new file mode 100644 (file)
index 0000000..6f32cff
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "surface_builder.h"
+#include "xdg_shell_stable.h"
+
+auto wlcs::SurfaceBuilder::all_surface_types() -> std::vector<std::shared_ptr<SurfaceBuilder>>
+{
+    return {
+        std::make_shared<WlShellSurfaceBuilder>(),
+        std::make_shared<XdgV6SurfaceBuilder>(),
+        std::make_shared<XdgStableSurfaceBuilder>(0, 0, 0, 0),
+        std::make_shared<XdgStableSurfaceBuilder>(12, 5, 20, 6),
+        std::make_shared<SubsurfaceBuilder>(std::make_pair(0, 0)),
+        std::make_shared<SubsurfaceBuilder>(std::make_pair(7, 12))};
+}
+
+auto wlcs::SurfaceBuilder::toplevel_surface_types() -> std::vector<std::shared_ptr<SurfaceBuilder>>
+{
+    return {
+        std::make_shared<WlShellSurfaceBuilder>(),
+        std::make_shared<XdgV6SurfaceBuilder>(),
+        std::make_shared<XdgStableSurfaceBuilder>(0, 0, 0, 0)};
+}
+
+auto wlcs::WlShellSurfaceBuilder::build(
+    wlcs::Server& server,
+    wlcs::Client& client,
+    std::pair<int, int> position,
+    std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface>
+{
+    auto surface = std::make_unique<wlcs::Surface>(
+        client.create_wl_shell_surface(size.first, size.second));
+    server.move_surface_to(*surface, position.first, position.second);
+    return surface;
+}
+
+auto wlcs::XdgV6SurfaceBuilder::build(
+    wlcs::Server& server,
+    wlcs::Client& client,
+    std::pair<int, int> position,
+    std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface>
+{
+    auto surface = std::make_unique<wlcs::Surface>(
+        client.create_xdg_shell_v6_surface(size.first, size.second));
+    server.move_surface_to(*surface, position.first, position.second);
+    return surface;
+}
+
+wlcs::XdgStableSurfaceBuilder::XdgStableSurfaceBuilder(int left_offset, int top_offset, int right_offset, int bottom_offset)
+    : SurfaceBuilder{"xdg_surface (stable)" +
+          ((left_offset || top_offset || right_offset || bottom_offset) ? (" {" +
+              std::to_string(left_offset) + ":" +
+              std::to_string(top_offset) + ":" +
+              std::to_string(right_offset) + ":" +
+              std::to_string(bottom_offset) + "}") : "")},
+      left_offset{left_offset},
+      top_offset{top_offset},
+      right_offset{right_offset},
+      bottom_offset{bottom_offset}
+{
+}
+
+auto wlcs::XdgStableSurfaceBuilder::build(
+    wlcs::Server& server,
+    wlcs::Client& client,
+    std::pair<int, int> position,
+    std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface>
+{
+    auto surface = std::make_unique<wlcs::Surface>(client);
+    auto xdg_surface = std::make_shared<wlcs::XdgSurfaceStable>(client, *surface);
+    auto xdg_toplevel = std::make_shared<wlcs::XdgToplevelStable>(*xdg_surface);
+    // The logical window will be shrunk and moved based on the offset, but the underlying surface will not
+    xdg_surface_set_window_geometry(
+        *xdg_surface,
+        left_offset, top_offset,
+        size.first - left_offset - right_offset, size.second -bottom_offset - top_offset);
+    surface->attach_visible_buffer(size.first, size.second);
+    surface->run_on_destruction(
+        [xdg_surface, xdg_toplevel]() mutable
+        {
+            xdg_toplevel.reset();
+            xdg_surface.reset();
+        });
+    server.move_surface_to(*surface, position.first + left_offset, position.second + top_offset);
+    return surface;
+}
+
+wlcs::SubsurfaceBuilder::SubsurfaceBuilder(std::pair<int, int> offset)
+    : SurfaceBuilder{
+        "subsurface (offset " +
+        std::to_string(offset.first) +
+        ", " +
+        std::to_string(offset.second) +
+        ")"},
+        offset{offset}
+{
+}
+
+auto wlcs::SubsurfaceBuilder::build(
+    wlcs::Server& server,
+    wlcs::Client& client,
+    std::pair<int, int> position,
+    std::pair<int, int> size) const -> std::unique_ptr<wlcs::Surface>
+{
+    auto main_surface = std::make_shared<wlcs::Surface>(
+        client.create_visible_surface(80, 50));
+    server.move_surface_to(
+        *main_surface,
+        position.first - offset.first,
+        position.second - offset.second);
+    client.run_on_destruction(
+        [main_surface]() mutable
+        {
+            main_surface.reset();
+        });
+    auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+        *main_surface,
+        offset.first, offset.second,
+        size.first, size.second));
+    // if subsurface is sync, tests would have to commit the parent to modify it
+    // this is inconvenient to do in a generic way, so we make it desync
+    wl_subsurface_set_desync(*subsurface);
+    return subsurface;
+}
+
+std::ostream& std::operator<<(std::ostream& out, std::shared_ptr<wlcs::SurfaceBuilder> const& param)
+{
+    return out << param->name;
+}
diff --git a/src/termcolor.hpp b/src/termcolor.hpp
new file mode 100644 (file)
index 0000000..f8a8629
--- /dev/null
@@ -0,0 +1,557 @@
+//!
+//! termcolor
+//! ~~~~~~~~~
+//!
+//! termcolor is a header-only c++ library for printing colored messages
+//! to the terminal. Written just for fun with a help of the Force.
+//!
+//! :copyright: (c) 2013 by Ihor Kalnytskyi
+//! :license: BSD, see LICENSE for details
+//!
+
+#ifndef TERMCOLOR_HPP_
+#define TERMCOLOR_HPP_
+
+// the following snippet of code detects the current OS and
+// defines the appropriate macro that is used to wrap some
+// platform specific things
+#if defined(_WIN32) || defined(_WIN64)
+#   define TERMCOLOR_OS_WINDOWS
+#elif defined(__APPLE__)
+#   define TERMCOLOR_OS_MACOS
+#elif defined(__unix__) || defined(__unix)
+#   define TERMCOLOR_OS_LINUX
+#else
+#   error unsupported platform
+#endif
+
+
+// This headers provides the `isatty()`/`fileno()` functions,
+// which are used for testing whether a standart stream refers
+// to the terminal. As for Windows, we also need WinApi funcs
+// for changing colors attributes of the terminal.
+#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+#   include <unistd.h>
+#elif defined(TERMCOLOR_OS_WINDOWS)
+#   include <io.h>
+#   include <windows.h>
+#endif
+
+
+#include <iostream>
+#include <cstdio>
+
+
+
+namespace termcolor
+{
+    // Forward declaration of the `_internal` namespace.
+    // All comments are below.
+    namespace _internal
+    {
+        // An index to be used to access a private storage of I/O streams. See
+        // colorize / nocolorize I/O manipulators for details.
+        static int colorize_index = std::ios_base::xalloc();
+
+        inline FILE* get_standard_stream(const std::ostream& stream);
+        inline bool is_colorized(std::ostream& stream);
+        inline bool is_atty(const std::ostream& stream);
+
+    #if defined(TERMCOLOR_OS_WINDOWS)
+        inline void win_change_attributes(std::ostream& stream, int foreground, int background=-1);
+    #endif
+    }
+
+    inline
+    std::ostream& colorize(std::ostream& stream)
+    {
+        stream.iword(_internal::colorize_index) = 1L;
+        return stream;
+    }
+
+    inline
+    std::ostream& nocolorize(std::ostream& stream)
+    {
+        stream.iword(_internal::colorize_index) = 0L;
+        return stream;
+    }
+
+    inline
+    std::ostream& reset(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[00m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream, -1, -1);
+        #endif
+        }
+        return stream;
+    }
+
+
+    inline
+    std::ostream& bold(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[1m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+        #endif
+        }
+        return stream;
+    }
+
+
+    inline
+    std::ostream& dark(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[2m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+        #endif
+        }
+        return stream;
+    }
+
+
+    inline
+    std::ostream& underline(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[4m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+        #endif
+        }
+        return stream;
+    }
+
+
+    inline
+    std::ostream& blink(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[5m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+        #endif
+        }
+        return stream;
+    }
+
+
+    inline
+    std::ostream& reverse(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[7m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+        #endif
+        }
+        return stream;
+    }
+
+
+    inline
+    std::ostream& concealed(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[8m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+        #endif
+        }
+        return stream;
+    }
+
+
+    inline
+    std::ostream& grey(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[30m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream,
+                0   // grey (black)
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& red(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[31m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream,
+                FOREGROUND_RED
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& green(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[32m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream,
+                FOREGROUND_GREEN
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& yellow(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[33m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream,
+                FOREGROUND_GREEN | FOREGROUND_RED
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& blue(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[34m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream,
+                FOREGROUND_BLUE
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& magenta(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[35m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream,
+                FOREGROUND_BLUE | FOREGROUND_RED
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& cyan(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[36m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream,
+                FOREGROUND_BLUE | FOREGROUND_GREEN
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& white(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[37m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream,
+                FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
+            );
+        #endif
+        }
+        return stream;
+    }
+
+
+
+    inline
+    std::ostream& on_grey(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[40m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream, -1,
+                0   // grey (black)
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& on_red(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[41m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream, -1,
+                BACKGROUND_RED
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& on_green(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[42m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream, -1,
+                BACKGROUND_GREEN
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& on_yellow(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[43m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream, -1,
+                BACKGROUND_GREEN | BACKGROUND_RED
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& on_blue(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[44m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream, -1,
+                BACKGROUND_BLUE
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& on_magenta(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[45m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream, -1,
+                BACKGROUND_BLUE | BACKGROUND_RED
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& on_cyan(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[46m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream, -1,
+                BACKGROUND_GREEN | BACKGROUND_BLUE
+            );
+        #endif
+        }
+        return stream;
+    }
+
+    inline
+    std::ostream& on_white(std::ostream& stream)
+    {
+        if (_internal::is_colorized(stream))
+        {
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            stream << "\033[47m";
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            _internal::win_change_attributes(stream, -1,
+                BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED
+            );
+        #endif
+        }
+
+        return stream;
+    }
+
+
+
+    //! Since C++ hasn't a way to hide something in the header from
+    //! the outer access, I have to introduce this namespace which
+    //! is used for internal purpose and should't be access from
+    //! the user code.
+    namespace _internal
+    {
+        //! Since C++ hasn't a true way to extract stream handler
+        //! from the a given `std::ostream` object, I have to write
+        //! this kind of hack.
+        inline
+        FILE* get_standard_stream(const std::ostream& stream)
+        {
+            if (&stream == &std::cout)
+                return stdout;
+            else if ((&stream == &std::cerr) || (&stream == &std::clog))
+                return stderr;
+
+            return 0;
+        }
+
+        // Say whether a given stream should be colorized or not. It's always
+        // true for ATTY streams and may be true for streams marked with
+        // colorize flag.
+        inline
+        bool is_colorized(std::ostream& stream)
+        {
+            return is_atty(stream) || static_cast<bool>(stream.iword(colorize_index));
+        }
+
+        //! Test whether a given `std::ostream` object refers to
+        //! a terminal.
+        inline
+        bool is_atty(const std::ostream& stream)
+        {
+            FILE* std_stream = get_standard_stream(stream);
+
+            // Unfortunately, fileno() ends with segmentation fault
+            // if invalid file descriptor is passed. So we need to
+            // handle this case gracefully and assume it's not a tty
+            // if standard stream is not detected, and 0 is returned.
+            if (!std_stream)
+                return false;
+
+        #if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX)
+            return ::isatty(fileno(std_stream));
+        #elif defined(TERMCOLOR_OS_WINDOWS)
+            return ::_isatty(_fileno(std_stream));
+        #endif
+        }
+
+    #if defined(TERMCOLOR_OS_WINDOWS)
+        //! Change Windows Terminal colors attribute. If some
+        //! parameter is `-1` then attribute won't changed.
+        inline void win_change_attributes(std::ostream& stream, int foreground, int background)
+        {
+            // yeah, i know.. it's ugly, it's windows.
+            static WORD defaultAttributes = 0;
+
+            // Windows doesn't have ANSI escape sequences and so we use special
+            // API to change Terminal output color. That means we can't
+            // manipulate colors by means of "std::stringstream" and hence
+            // should do nothing in this case.
+            if (!_internal::is_atty(stream))
+                return;
+
+            // get terminal handle
+            HANDLE hTerminal = INVALID_HANDLE_VALUE;
+            if (&stream == &std::cout)
+                hTerminal = GetStdHandle(STD_OUTPUT_HANDLE);
+            else if (&stream == &std::cerr)
+                hTerminal = GetStdHandle(STD_ERROR_HANDLE);
+
+            // save default terminal attributes if it unsaved
+            if (!defaultAttributes)
+            {
+                CONSOLE_SCREEN_BUFFER_INFO info;
+                if (!GetConsoleScreenBufferInfo(hTerminal, &info))
+                    return;
+                defaultAttributes = info.wAttributes;
+            }
+
+            // restore all default settings
+            if (foreground == -1 && background == -1)
+            {
+                SetConsoleTextAttribute(hTerminal, defaultAttributes);
+                return;
+            }
+
+            // get current settings
+            CONSOLE_SCREEN_BUFFER_INFO info;
+            if (!GetConsoleScreenBufferInfo(hTerminal, &info))
+                return;
+
+            if (foreground != -1)
+            {
+                info.wAttributes &= ~(info.wAttributes & 0x0F);
+                info.wAttributes |= static_cast<WORD>(foreground);
+            }
+
+            if (background != -1)
+            {
+                info.wAttributes &= ~(info.wAttributes & 0xF0);
+                info.wAttributes |= static_cast<WORD>(background);
+            }
+
+            SetConsoleTextAttribute(hTerminal, info.wAttributes);
+        }
+    #endif // TERMCOLOR_OS_WINDOWS
+
+    } // namespace _internal
+
+} // namespace termcolor
+
+
+#undef TERMCOLOR_OS_WINDOWS
+#undef TERMCOLOR_OS_MACOS
+#undef TERMCOLOR_OS_LINUX
+
+#endif // TERMCOLOR_HPP_
diff --git a/src/test_c_compile.c b/src/test_c_compile.c
new file mode 100644 (file)
index 0000000..a0bab36
--- /dev/null
@@ -0,0 +1,7 @@
+#include "wlcs/display_server.h"
+#include "wlcs/pointer.h"
+#include "wlcs/touch.h"
+
+WlcsServerIntegration server_integration;
+WlcsPointer pointer;
+WlcsTouch touch;
diff --git a/src/thread_proxy.h b/src/thread_proxy.h
new file mode 100644 (file)
index 0000000..93e678c
--- /dev/null
@@ -0,0 +1,465 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_THREAD_PROXY_H_
+#define WLCS_THREAD_PROXY_H_
+
+#include <wayland-server-core.h>
+#include <functional>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <mutex>
+#include <thread>
+#include <tuple>
+#include <boost/throw_exception.hpp>
+
+/*
+ * ThreadProxy: Take a Callable and run it on the Wayland event loop.
+ *
+ * There are two main parts here: some template metaprogramming, and
+ * the ThreadProxy class.
+ *
+ * ThreadProxy maintains the infrastructure for taking Callables
+ * and invoking them on a Wayland event loop. The meat here is
+ * in register_op(), which takes a Callable and returns a
+ * new Callable that, when invoked, will result in the original
+ * Callable being called on the Wayland event loop.
+ *
+ * The mechanism used for invoking on the Wayland event loop is a
+ * socket: arguments are serialised to the socket, and then deserialised
+ * when processed by the event loop.
+ *
+ * The template metaprogramming is mostly for manipulating parameter
+ * packs: serialising them to and from linear buffers (so that they
+ * can be passed across a socket), and invoking a Callable with a
+ * tuple of args, and deducing the arguments and return value of
+ * a Callable
+ *
+ */
+
+namespace
+{
+/*
+ * Deduce the argument types and return values of a Callable.
+ *
+ * Notably this only works for Callables with exactly one implementation
+ * of operator(), otherwise template deduction will fail as it is unable
+ * to select which operator() to operate on.
+ *
+ * This isn't an issue for what we're using this with, which is primarly
+ * lambdas.
+ */
+template<typename Callable>
+struct callable_traits : public callable_traits<decltype(&Callable::operator())> {};
+
+template<typename Callable, typename Returns, typename... Args>
+struct callable_traits<Returns(Callable::*)(Args...) const>
+{
+    using return_type = Returns;
+    using args = typename std::tuple<Args...>;
+};
+
+/*
+ * call_helper and call are downmarket versions of C++17's std::invoke()
+ *
+ * We don't yet require C++17, so downmarket it is!
+ */
+template<typename Returns, typename Callable, typename... Args, std::size_t... I>
+Returns call_helper(Callable const& func, std::tuple<Args...> const& args, std::index_sequence<I...>)
+{
+    return func(std::get<I>(args)...);
+}
+
+template<typename Returns, typename Callable, typename... Args>
+Returns call(Callable const& func, std::tuple<Args...> const& args)
+{
+    return call_helper<Returns>(func, args, std::index_sequence_for<Args...>{});
+}
+
+/*
+ * tuple_from_buffer base case: To unpack a buffer containing no values,
+ * return a std::tuple<>.
+ */
+template<
+    typename... Args,
+    typename std::enable_if<sizeof...(Args) == 0, int>::type = 0>
+std::tuple<Args...> tuple_from_buffer(char*, std::tuple<Args...> const*)
+{
+    return std::tuple<Args...>{};
+}
+
+/*
+ * Unpack a linear buffer into a std::tuple of its component types.
+ */
+template<typename Head, typename... Tail>
+std::tuple<Head, Tail...> tuple_from_buffer(char* buffer, std::tuple<Head, Tail...> const*)
+{
+    // We need to memcpy out of the buffer as there's no guarantee data in the buffer has correct
+    // alignment for this type.
+    Head aligned_arg;
+    ::memcpy(&aligned_arg, buffer, sizeof(Head));
+
+    std::tuple<Tail...> const* type_deducer = nullptr;
+    return std::tuple_cat(std::make_tuple(aligned_arg), tuple_from_buffer(buffer + sizeof(Head), type_deducer));
+}
+
+std::array<int, 2> setup_socketpair()
+{
+    std::array<int, 2> socket_fds;
+
+    if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, socket_fds.data()) < 0)
+    {
+        BOOST_THROW_EXCEPTION((
+            std::system_error{
+                errno,
+                std::system_category(),
+                "Failed to create Wayland thread communication socket"}));
+    }
+
+    return socket_fds;
+}
+
+constexpr ssize_t arguments_size()
+{
+    return 0;
+}
+
+template<typename T, typename... Remainder>
+constexpr ssize_t arguments_size(T const& head, Remainder const&... tail)
+{
+    return sizeof(head) + arguments_size(tail...);
+}
+
+/*
+ * pack_buffer base case: To pack no arguments into a buffer, do nothing.
+ */
+void pack_buffer(void*)
+{
+}
+
+/*
+ * Pack a any number of (TriviallyCopyable) arguments into a linear buffer.
+ */
+template<typename T, typename... Remainder>
+void pack_buffer(char* buffer, T&& arg, Remainder&& ...args)
+{
+    memcpy(buffer, &arg, sizeof(arg));
+    pack_buffer(buffer + sizeof(arg), std::forward<Remainder>(args)...);
+}
+
+/*
+ * A bit more C++17 backporting...
+ *
+ * This bit of metaprogramming is in support of statically-asserting that
+ * all the types passed through the proxy are TriviallyCopyable.
+ */
+template<class...>
+struct conjunction : std::true_type {};
+
+template<class Cond>
+struct conjunction<Cond> : Cond {};
+
+template<class Head, class... Tail>
+struct conjunction<Head, Tail...> :
+    std::conditional_t<bool(Head::value), conjunction<Tail...>, Head> {};
+
+template<typename... Args>
+constexpr bool all_args_are_trivially_copyable(std::tuple<Args...> const*)
+{
+    return conjunction<std::is_trivially_copyable<Args>...>::value;
+}
+
+/*
+ * ThreadProxy: a mechanism to take a functor from any thread, and turn it into
+ * a functor that runs on the Wayland event loop.
+ *
+ * Theory of operation:
+ * The inter-thread channel used is a SOCK_SEQPACKET socket.
+ *
+ * register_op() takes an (almost) arbitrary Callable, wraps it in a
+ * std::function<> that recieves a pointer to a buffer of arguments,
+ * unpacks the buffer into a std::tuple, then invokes the original
+ * Callable, and adds this function to the handler array.
+ *
+ * register_op() then *returns* a function with the same signature that
+ * copies the array index of the relevant handler into a falt buffer,
+ * marshalls the arguments passed in into the buffer and then pushes that buffer
+ * into the socket.
+ *
+ * On the Wayland event loop side, we add a fd event_source to the event_loop
+ * which reads from the Wayland end of the socket, extracts the index into the
+ * handler array, then calls the handler with the rest of the message buffer.
+ */
+class ThreadProxy : public std::enable_shared_from_this<ThreadProxy>
+{
+private:
+
+    template<typename... Args>
+    void send_message(uint32_t opcode, Args&& ...args) const
+    {
+        char buffer[max_message_size];
+        ssize_t const message_size = sizeof(uint32_t) + arguments_size(args...);
+
+        *reinterpret_cast<uint32_t*>(buffer) = opcode;
+        pack_buffer(buffer + sizeof(uint32_t), std::forward<Args>(args)...);
+
+        auto const written = send(fds[Fd::Client], buffer, message_size, MSG_DONTWAIT);
+
+        if (written < message_size)
+        {
+            if (written < 0)
+            {
+                BOOST_THROW_EXCEPTION((
+                    std::system_error{
+                        errno,
+                        std::system_category(),
+                        "Failed to send message to Wayland thread"}));
+            }
+            BOOST_THROW_EXCEPTION((
+                std::runtime_error{
+                    "Failed to send whole message to Wayland thread"}));
+        }
+    }
+
+    template<typename T>
+    T wait_for_reply() const
+    {
+        T buffer;
+        // Wait for it to be processed
+        auto const read = recv(fds[Fd::Client], &buffer, sizeof(buffer), 0);
+        if (read < static_cast<ssize_t>(sizeof(buffer)))
+        {
+            if (read < 0)
+            {
+                BOOST_THROW_EXCEPTION((
+                    std::system_error{
+                        errno,
+                        std::system_category(),
+                        "Failed to receive reply from Wayland thread"}));
+            }
+            BOOST_THROW_EXCEPTION((
+                std::runtime_error{
+                    "Received short reply from Wayland thread"}));
+        }
+        return buffer;
+    }
+
+    template<typename... Args>
+    auto make_send_functor(uint32_t opcode, std::tuple<Args...> const*) const
+    {
+        return
+            [me = this->shared_from_this(), opcode](Args&& ...args)
+            {
+                // We technically don't need to serialise the send/receive, but
+                // it's a bit easier if only one request is in flight at once.
+                std::lock_guard<std::mutex> lock{me->message_serialiser};
+                me->send_message(opcode, std::forward<Args>(args)...);
+                me->wait_for_reply<char>();
+            };
+    }
+
+    template<typename Returns, typename... Args>
+    auto make_send_functor(uint32_t opcode, std::tuple<Args...> const*) const
+    {
+        return
+            [me = this->shared_from_this(), opcode](Args&& ...args)
+            {
+                // We technically don't need to serialise the send/receive, but
+                // it's a bit easier if only one request is in flight at once.
+                std::lock_guard<std::mutex> lock{me->message_serialiser};
+                me->send_message(opcode, std::forward<Args>(args)...);
+                return me->wait_for_reply<Returns>();
+            };
+    }
+
+    template<
+        typename Callable,
+        typename... Args,
+        typename
+        std::enable_if_t<
+            std::is_same<
+                typename callable_traits<typename std::decay<Callable>::type>::return_type,
+                void>::value,
+            int> = 0>
+    auto make_recv_functor(Callable handler, std::tuple<Args...> const*)
+    {
+        return
+            [this, handler = std::move(handler)](void* data)
+            {
+                std::tuple<Args...> const* type_resolver = nullptr;
+                auto const args = tuple_from_buffer(static_cast<char*>(data), type_resolver);
+
+                call<void>(handler, args);
+
+                // void functions send a dummy char, to notify the other end that
+                // the call has completed.
+                char const dummy{0};
+                send(fds[Fd::Wayland], &dummy, sizeof(dummy), 0);
+            };
+    }
+
+    template<
+        typename Callable,
+        typename... Args,
+        typename
+        std::enable_if_t<
+            !std::is_same<
+                typename callable_traits<typename std::decay<Callable>::type>::return_type,
+                void>::value, int> = 0>
+    auto make_recv_functor(Callable handler, std::tuple<Args...> const*)
+    {
+        return
+            [this, handler = std::move(handler)](void* data)
+            {
+                using traits = callable_traits<typename std::decay<Callable>::type>;
+
+                std::tuple<Args...> const* type_resolver = nullptr;
+                auto const args = tuple_from_buffer(static_cast<char*>(data), type_resolver);
+
+                auto const val = call<typename traits::return_type>(handler, args);
+
+                send(this->fds[Fd::Wayland], &val, sizeof(val), 0);
+            };
+    }
+public:
+    template<
+        typename Callable,
+        typename
+        std::enable_if_t<
+            std::is_same<
+                typename callable_traits<typename std::decay<Callable>::type>::return_type,
+                void>::value, int> = 0>
+    auto register_op(Callable handler)
+    {
+        using traits = callable_traits<typename std::decay<Callable>::type>;
+        // This is technically too restrictive; std::tuple<Args...> might have a size greater
+        // than the sum of its parts.
+        static_assert(
+            sizeof(typename traits::args) < max_arguments_size,
+            "Attempt to call function with too many arguments; bump max_message_size");
+
+
+        // Get template deduction to work by passing an (unused) argument of type
+        // std::tuple<Args...>*; we can't access Args... ourselves here.
+        constexpr typename traits::args const* type_resolver = nullptr;
+
+        static_assert(
+            all_args_are_trivially_copyable(type_resolver),
+            "All arguments of register_op must be TriviallyCopyable as they must support memcpy");
+
+        auto const next_opcode = handlers.size();
+        handlers.emplace_back(make_recv_functor(std::move(handler), type_resolver));
+
+        return make_send_functor(next_opcode, type_resolver);
+    }
+
+    template<
+        typename Callable,
+        typename
+        std::enable_if_t<
+            !std::is_same<
+                typename callable_traits<typename std::decay<Callable>::type>::return_type,
+                void>::value, int> = 0>
+    auto register_op(Callable handler)
+    {
+        using traits = callable_traits<typename std::decay<Callable>::type>;
+        static_assert(
+            sizeof(typename traits::args) < max_arguments_size,
+            "Attempt to call function with too many arguments; bump max_message_size");
+        static_assert(
+            sizeof(typename traits::return_type) < max_message_size,
+            "Attempt to call function with too large return value; bump max_message_size");
+
+        // Get template deduction to work by passing an (unused) argument of type
+        // std::tuple<Args...>; we can't access Args... ourselves here.
+        constexpr typename traits::args const* type_resolver = nullptr;
+        static_assert(
+            all_args_are_trivially_copyable(type_resolver),
+            "All arguments of register_op must be TriviallyCopyable as they must support memcpy");
+
+        auto const next_opcode = handlers.size();
+        handlers.emplace_back(make_recv_functor(std::move(handler), type_resolver));
+
+        return make_send_functor<typename traits::return_type>(next_opcode, type_resolver);
+    }
+
+    explicit ThreadProxy(struct wl_event_loop* event_loop)
+        : fds{setup_socketpair()},
+          source{
+              wl_event_loop_add_fd(
+                  event_loop,
+                  fds[Fd::Wayland],
+                  WL_EVENT_READABLE,
+                  &ThreadProxy::socket_readable,
+                  this)},
+        // We have to manually implement the destructor() thunk rather than using register_op because
+        // the send_functor returned by register_op takes shared reference to this, care of
+        // shared_from_this(). But during construction `this` is not a shared_ptr, so shared_from_this()
+        // isn't usable.
+          destructor{
+              [this]()
+              {
+                  send_message(0);
+              }}
+    {
+        // We must run wl_event_source_remove() on the Wayland mainloop, too.
+        handlers.emplace_back(
+            [this](void*)
+            {
+                wl_event_source_remove(source);
+            });
+    }
+
+    ~ThreadProxy()
+    {
+        destructor();
+        close(fds[0]);
+        close(fds[1]);
+    }
+
+private:
+    static int socket_readable(int fd, uint32_t /*mask*/, void* data) noexcept
+    {
+        auto const& me = *static_cast<ThreadProxy*>(data);
+        char buffer[max_message_size];
+
+        recv(fd, buffer, sizeof(buffer), 0);
+
+        auto const opcode = *reinterpret_cast<uint32_t*>(buffer);
+        me.handlers[opcode](buffer + sizeof(uint32_t));
+
+        return 0;
+    }
+
+    static constexpr ssize_t max_arguments_size{1024};
+    static constexpr ssize_t max_message_size = max_arguments_size + sizeof(uint32_t);
+    enum Fd
+    {
+        Wayland = 0,
+        Client
+    };
+    std::array<int, 2> const fds;
+    struct wl_event_source* const source;
+    std::mutex mutable message_serialiser;
+    std::vector<std::function<void(void*)>> handlers;
+    std::function<void()> const destructor;
+};
+}
+
+
+#endif //WLCS_THREAD_PROXY_H_
diff --git a/src/version_specifier.cpp b/src/version_specifier.cpp
new file mode 100644 (file)
index 0000000..3009ed5
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include "version_specifier.h"
+
+using std::experimental::optional;
+
+wlcs::ExactlyVersion::ExactlyVersion(uint32_t version) noexcept
+    : version{version}
+{
+}
+
+auto wlcs::ExactlyVersion::select_version(uint32_t max_version) const -> optional<uint32_t>
+{
+    if (max_version < version)
+    {
+        return {};
+    }
+    return {version};
+}
+
+auto wlcs::ExactlyVersion::describe() const -> std::string
+{
+    return std::string{"= "} + std::to_string(version);
+}
+
+wlcs::AtLeastVersion::AtLeastVersion(uint32_t version) noexcept
+    : version{version}
+{
+}
+
+auto wlcs::AtLeastVersion::select_version(uint32_t max_version) const -> optional<uint32_t>
+{
+    if (max_version < version)
+    {
+        return {};
+    }
+    return {max_version};
+}
+
+auto wlcs::AtLeastVersion::describe() const -> std::string
+{
+    return std::string{">= "} + std::to_string(version);
+}
+
+wlcs::VersionSpecifier const& wlcs::AnyVersion = wlcs::AtLeastVersion{1};
diff --git a/src/xdg_output_v1.cpp b/src/xdg_output_v1.cpp
new file mode 100644 (file)
index 0000000..a92e469
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "xdg_output_v1.h"
+#include "wl_handle.h"
+#include "version_specifier.h"
+#include <boost/throw_exception.hpp>
+
+struct wlcs::XdgOutputManagerV1::Impl
+{
+    Impl(Client& client)
+        : client{client},
+          manager{client.bind_if_supported<zxdg_output_manager_v1>(AnyVersion)}
+    {
+    }
+
+    Client& client;
+    WlHandle<zxdg_output_manager_v1> const manager;
+};
+
+wlcs::XdgOutputManagerV1::XdgOutputManagerV1(Client& client)
+    : impl{std::make_unique<Impl>(client)}
+{
+}
+
+wlcs::XdgOutputManagerV1::~XdgOutputManagerV1() = default;
+
+wlcs::XdgOutputManagerV1::operator zxdg_output_manager_v1*() const
+{
+    return impl->manager;
+}
+
+auto wlcs::XdgOutputManagerV1::client() const -> Client&
+{
+    return impl->client;
+}
+
+struct wlcs::XdgOutputV1::Impl
+{
+    Impl(XdgOutputManagerV1& manager, size_t output_index)
+        : output{zxdg_output_manager_v1_get_xdg_output(
+              manager,
+              manager.client().output_state(output_index).output)},
+          version{zxdg_output_v1_get_version(output)}
+    {
+        zxdg_output_v1_add_listener(output, &Impl::listener, this);
+    }
+
+    ~Impl()
+    {
+        zxdg_output_v1_destroy(output);
+    }
+
+    zxdg_output_v1* const output;
+    uint32_t const version;
+    bool dirty{false};
+    State _state;
+
+    static zxdg_output_v1_listener const listener;
+};
+
+zxdg_output_v1_listener const wlcs::XdgOutputV1::Impl::listener = {
+    [] /* logical_position */ (void *data, zxdg_output_v1 *, int32_t x, int32_t y)
+    {
+        auto const impl = static_cast<Impl*>(data);
+        impl->_state.logical_position = std::make_pair(x, y);
+        impl->dirty = true;
+    },
+
+    [] /* logical_size */ (void *data, zxdg_output_v1 *, int32_t width, int32_t height)
+    {
+        auto const impl = static_cast<Impl*>(data);
+        impl->_state.logical_size = std::make_pair(width, height);
+        impl->dirty = true;
+    },
+
+    [] /* done */ (void *data, zxdg_output_v1 *)
+    {
+        auto const impl = static_cast<Impl*>(data);
+        if (impl->version < 3)
+            impl->dirty = false;
+    },
+
+    [] /* name */ (void *data, zxdg_output_v1 *, const char *name)
+    {
+        auto const impl = static_cast<Impl*>(data);
+        std::string str{name};
+        impl->_state.name = str;
+        impl->dirty = true;
+    },
+
+    [] /* description */ (void *data, zxdg_output_v1 *, const char *description)
+    {
+        auto const impl = static_cast<Impl*>(data);
+        impl->_state.description = std::string{description};
+        impl->dirty = true;
+    },
+};
+
+wlcs::XdgOutputV1::XdgOutputV1(XdgOutputManagerV1& manager, size_t output_index)
+    : impl{std::make_shared<Impl>(manager, output_index)}
+{
+    if (impl->version >= 3)
+    {
+        manager.client().add_output_done_notifier(output_index, [weak = std::weak_ptr<Impl>(impl)]()
+            {
+                if (auto const impl = weak.lock())
+                {
+                    impl->dirty = false;
+                }
+            });
+    }
+}
+
+wlcs::XdgOutputV1::operator zxdg_output_v1*() const
+{
+    return impl->output;
+}
+
+auto wlcs::XdgOutputV1::state() -> State const&
+{
+    if (impl->dirty)
+    {
+        std::string done_event = (impl->version >= 3 ? "wl_output.done" : "zxdg_output_v1.done");
+        BOOST_THROW_EXCEPTION(std::logic_error("State change was not finished with a " + done_event + " event"));
+    }
+
+    return impl->_state;
+}
diff --git a/src/xdg_shell_stable.cpp b/src/xdg_shell_stable.cpp
new file mode 100644 (file)
index 0000000..a9b66f3
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "xdg_shell_stable.h"
+
+// XdgSurfaceStable
+
+wlcs::XdgSurfaceStable::XdgSurfaceStable(wlcs::Client& client, wlcs::Surface& surface)
+{
+    if (!client.xdg_shell_stable())
+        throw std::runtime_error("XDG shell stable not supported by compositor");
+    shell_surface = xdg_wm_base_get_xdg_surface(client.xdg_shell_stable(), surface);
+    static struct xdg_surface_listener const listener = {configure_thunk};
+    xdg_surface_add_listener(shell_surface, &listener, this);
+}
+
+wlcs::XdgSurfaceStable::~XdgSurfaceStable()
+{
+    xdg_surface_destroy(shell_surface);
+}
+
+// XdgToplevelStable
+
+wlcs::XdgToplevelStable::State::State(int32_t width, int32_t height, struct wl_array *states)
+    : width{width},
+      height{height},
+      maximized{false},
+      fullscreen{false},
+      resizing{false},
+      activated{false}
+{
+    if (!states)
+        return;
+    xdg_toplevel_state* item;
+    for (item = static_cast<xdg_toplevel_state*>(states->data);
+         (char*)item < static_cast<char*>(states->data) + states->size;
+         item++)
+    {
+        switch (*item)
+        {
+            case XDG_TOPLEVEL_STATE_MAXIMIZED:
+                maximized = true;
+                break;
+            case XDG_TOPLEVEL_STATE_FULLSCREEN:
+                fullscreen = true;
+                break;
+            case XDG_TOPLEVEL_STATE_RESIZING:
+                resizing = true;
+                break;
+            case XDG_TOPLEVEL_STATE_ACTIVATED:
+                activated = true;
+                break;
+        }
+    }
+}
+
+wlcs::XdgToplevelStable::XdgToplevelStable(XdgSurfaceStable& shell_surface_)
+    : shell_surface{&shell_surface_}
+{
+    toplevel = xdg_surface_get_toplevel(*shell_surface);
+    static struct xdg_toplevel_listener const listener = {configure_thunk, close_thunk};
+    xdg_toplevel_add_listener(toplevel, &listener, this);
+}
+
+wlcs::XdgToplevelStable::~XdgToplevelStable()
+{
+    xdg_toplevel_destroy(toplevel);
+}
+
+wlcs::XdgPositionerStable::XdgPositionerStable(wlcs::Client& client)
+    : positioner{xdg_wm_base_create_positioner(client.xdg_shell_stable())}
+{
+}
+
+wlcs::XdgPositionerStable::~XdgPositionerStable()
+{
+    xdg_positioner_destroy(positioner);
+}
+
+wlcs::XdgPopupStable::XdgPopupStable(
+    XdgSurfaceStable& shell_surface_,
+    std::experimental::optional<XdgSurfaceStable*> parent,
+    XdgPositionerStable& positioner)
+    : shell_surface{&shell_surface_},
+      popup{xdg_surface_get_popup(
+          *shell_surface,
+          parent ? *parent.value() : (xdg_surface*)nullptr,
+          positioner)}
+{
+    static struct xdg_popup_listener const listener = {configure_thunk, popup_done_thunk};
+    xdg_popup_add_listener(popup, &listener, this);
+}
+
+wlcs::XdgPopupStable::~XdgPopupStable()
+{
+    xdg_popup_destroy(popup);
+}
diff --git a/src/xdg_shell_v6.cpp b/src/xdg_shell_v6.cpp
new file mode 100644 (file)
index 0000000..adf9b19
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "xdg_shell_v6.h"
+
+// XdgSurfaceV6
+
+wlcs::XdgSurfaceV6::XdgSurfaceV6(wlcs::Client& client, wlcs::Surface& surface)
+{
+    if (!client.xdg_shell_v6())
+        throw std::runtime_error("XDG shell unstable V6 not supported by compositor");
+    shell_surface = zxdg_shell_v6_get_xdg_surface(client.xdg_shell_v6(), surface);
+    static struct zxdg_surface_v6_listener const listener = {configure_thunk};
+    zxdg_surface_v6_add_listener(shell_surface, &listener, this);
+}
+
+wlcs::XdgSurfaceV6::~XdgSurfaceV6()
+{
+    zxdg_surface_v6_destroy(shell_surface);
+}
+
+// XdgToplevelV6
+
+wlcs::XdgToplevelV6::State::State(int32_t width, int32_t height, struct wl_array *states)
+    : width{width},
+      height{height},
+      maximized{false},
+      fullscreen{false},
+      resizing{false},
+      activated{false}
+{
+    if (!states)
+        return;
+    zxdg_toplevel_v6_state* item;
+    for (item = static_cast<zxdg_toplevel_v6_state*>(states->data);
+         (char*)item < static_cast<char*>(states->data) + states->size;
+         item++)
+    {
+        switch (*item)
+        {
+            case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
+                maximized = true;
+                break;
+            case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
+                fullscreen = true;
+                break;
+            case ZXDG_TOPLEVEL_V6_STATE_RESIZING:
+                resizing = true;
+                break;
+            case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
+                activated = true;
+                break;
+        }
+    }
+}
+
+wlcs::XdgToplevelV6::XdgToplevelV6(XdgSurfaceV6& shell_surface_)
+    : shell_surface{&shell_surface_}
+{
+    toplevel = zxdg_surface_v6_get_toplevel(*shell_surface);
+    static struct zxdg_toplevel_v6_listener const listener = {configure_thunk, close_thunk};
+    zxdg_toplevel_v6_add_listener(toplevel, &listener, this);
+}
+
+wlcs::XdgToplevelV6::~XdgToplevelV6()
+{
+    zxdg_toplevel_v6_destroy(toplevel);
+}
+
+wlcs::XdgPositionerV6::XdgPositionerV6(wlcs::Client& client)
+    : positioner{zxdg_shell_v6_create_positioner(client.xdg_shell_v6())}
+{
+}
+
+wlcs::XdgPositionerV6::~XdgPositionerV6()
+{
+    zxdg_positioner_v6_destroy(positioner);
+}
+
+wlcs::XdgPopupV6::XdgPopupV6(XdgSurfaceV6& shell_surface_, XdgSurfaceV6& parent, XdgPositionerV6& positioner)
+    : shell_surface{&shell_surface_},
+      popup{zxdg_surface_v6_get_popup(*shell_surface, parent, positioner)}
+{
+    static struct zxdg_popup_v6_listener const listener = {configure_thunk, popup_done_thunk};
+    zxdg_popup_v6_add_listener(popup, &listener, this);
+}
+
+wlcs::XdgPopupV6::~XdgPopupV6()
+{
+    zxdg_popup_v6_destroy(popup);
+}
diff --git a/src/xfail_supporting_test_listener.cpp b/src/xfail_supporting_test_listener.cpp
new file mode 100644 (file)
index 0000000..77e1a64
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#include "xfail_supporting_test_listener.h"
+#include <gtest/gtest.h>
+#include <chrono>
+#include "termcolor.hpp"
+
+testing::XFailSupportingTestListenerWrapper::XFailSupportingTestListenerWrapper(std::unique_ptr<testing::TestEventListener>&& wrapped)
+        : delegate{std::move(wrapped)}
+{
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestProgramStart(testing::UnitTest const& unit_test)
+{
+    delegate->OnTestProgramStart(unit_test);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestIterationStart(testing::UnitTest const& unit_test, int iteration)
+{
+    failed_test_names.clear();
+    skipped_test_names.clear();
+    delegate->OnTestIterationStart(unit_test, iteration);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsSetUpStart(testing::UnitTest const& unit_test)
+{
+    delegate->OnEnvironmentsSetUpStart(unit_test);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsSetUpEnd(testing::UnitTest const& unit_test)
+{
+    delegate->OnEnvironmentsSetUpEnd(unit_test);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestCaseStart(testing::TestCase const& test_case)
+{
+    delegate->OnTestCaseStart(test_case);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestStart(testing::TestInfo const& test_info)
+{
+    current_test_info = &test_info;
+    current_test_start = std::chrono::steady_clock::now();
+    delegate->OnTestStart(test_info);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestPartResult(testing::TestPartResult const& test_part_result)
+{
+    if (test_part_result.failed())
+    {
+        // Search for expected-failure property
+        for (int i= 0; i < current_test_info->result()->test_property_count() ; ++i)
+        {
+            auto prop = current_test_info->result()->GetTestProperty(i);
+            if (strcmp(prop.key(), "wlcs-skip-test") == 0)
+            {
+                if (!current_skip_reasons)
+                {
+                    current_skip_reasons = std::vector<std::string>{};
+                }
+                current_skip_reasons->push_back(prop.value());
+            }
+        }
+        if (current_skip_reasons)
+        {
+            skipped_test_names.insert(std::string{current_test_info->test_case_name()} + "." + current_test_info->name());
+            return;
+        }
+
+        failed_test_names.insert(std::string{current_test_info->test_case_name()} + "." + current_test_info->name());
+    }
+    delegate->OnTestPartResult(test_part_result);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestEnd(testing::TestInfo const& test_info)
+{
+    if (current_skip_reasons)
+    {
+        for (auto const& reason : *current_skip_reasons)
+        {
+            std::cout
+                << termcolor::yellow << "[          ]"
+                << termcolor::reset << " " << reason << std::endl;
+        }
+        auto elapsed_time = std::chrono::steady_clock::now() - current_test_start;
+
+        std::cout
+            << termcolor::yellow << "[     SKIP ]"
+            << termcolor::reset << " "
+            << test_info.test_case_name() << "." << test_info.name()
+            << " ("
+            << std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time).count()
+            << "ms)" << std::endl;
+    }
+    else
+    {
+        delegate->OnTestEnd(test_info);
+    }
+    current_skip_reasons = {};
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestCaseEnd(testing::TestCase const& test_case)
+{
+    delegate->OnTestCaseEnd(test_case);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsTearDownStart(testing::UnitTest const& unit_test)
+{
+    delegate->OnEnvironmentsTearDownStart(unit_test);
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnEnvironmentsTearDownEnd(testing::UnitTest const& unit_test)
+{
+    delegate->OnEnvironmentsTearDownEnd(unit_test);
+}
+
+namespace
+{
+std::string singular_or_plural(char const* base, int count)
+{
+    if (count == 1)
+    {
+        return base;
+    }
+    return std::string{base} + "s";
+}
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestIterationEnd(testing::UnitTest const& unit_test, int /*iteration*/)
+{
+    std::cout
+        << termcolor::green << "[==========] "
+        << termcolor::reset
+        << unit_test.test_to_run_count()
+        << " tests from "
+        << unit_test.test_case_to_run_count()
+        << " test cases run. ("
+        << unit_test.elapsed_time()
+        << "ms total elapsed)" << std::endl;
+
+    std::cout
+        << termcolor::green << "[  PASSED  ]"
+        << termcolor::reset
+        << " "
+        << unit_test.successful_test_count()
+        << singular_or_plural(" test", unit_test.successful_test_count()) << std::endl;
+
+    auto const skipped_tests = skipped_test_names.size();
+    if (skipped_tests > 0)
+    {
+        std::cout
+            << termcolor::yellow << "[  SKIPPED ] "
+            << termcolor::reset
+            << skipped_tests
+            << singular_or_plural(" test", skipped_tests)
+            << " skipped:" << std::endl;
+        for (auto const& name : skipped_test_names)
+        {
+            std::cout
+                << termcolor::yellow << "[  SKIPPED ] "
+                << termcolor::reset << name << std::endl;
+        }
+    }
+    auto const failed_tests = failed_test_names.size();
+    if (failed_tests > 0)
+    {
+        /* Mark the test run as a failure; if multiple iterations are run
+         * only one might fail, making failed_test_names() an unreliable
+         * indicator.
+         */
+        failed_ = true;
+
+        std::cout
+            << termcolor::red << "[  FAILED  ] "
+            << termcolor::reset
+            << failed_tests
+            << singular_or_plural(" test", failed_tests)
+            << " failed:" << std::endl;
+        for (auto const& name : failed_test_names)
+        {
+            std::cout
+                << termcolor::red << "[  FAILED  ] "
+                << termcolor::reset << name << std::endl;
+        }
+    }
+}
+
+void testing::XFailSupportingTestListenerWrapper::OnTestProgramEnd(testing::UnitTest const& unit_test)
+{
+    delegate->OnTestProgramEnd(unit_test);
+}
+
+bool testing::XFailSupportingTestListenerWrapper::failed() const
+{
+    return failed_;
+}
diff --git a/src/xfail_supporting_test_listener.h b/src/xfail_supporting_test_listener.h
new file mode 100644 (file)
index 0000000..929ebe9
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
+ */
+
+#ifndef WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_
+#define WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_
+
+#include <gtest/gtest.h>
+
+#include <experimental/optional>
+#include <vector>
+#include <string>
+#include <chrono>
+#include <unordered_set>
+
+namespace testing
+{
+class XFailSupportingTestListenerWrapper : public testing::TestEventListener
+{
+public:
+    explicit XFailSupportingTestListenerWrapper(std::unique_ptr<testing::TestEventListener>&& wrapped);
+
+    void OnTestProgramStart(testing::UnitTest const& unit_test) override;
+
+    void OnTestIterationStart(testing::UnitTest const& unit_test, int iteration) override;
+
+    void OnEnvironmentsSetUpStart(testing::UnitTest const& unit_test) override;
+
+    void OnEnvironmentsSetUpEnd(testing::UnitTest const& unit_test) override;
+
+    void OnTestCaseStart(testing::TestCase const& test_case) override;
+
+    void OnTestStart(testing::TestInfo const& test_info) override;
+
+    void OnTestPartResult(testing::TestPartResult const& test_part_result) override;
+
+    void OnTestEnd(testing::TestInfo const& test_info) override;
+    void OnTestCaseEnd(testing::TestCase const& test_case) override;
+
+    void OnEnvironmentsTearDownStart(testing::UnitTest const& unit_test) override;
+
+    void OnEnvironmentsTearDownEnd(testing::UnitTest const& unit_test) override;
+
+    void OnTestIterationEnd(testing::UnitTest const& unit_test, int iteration) override;
+
+    void OnTestProgramEnd(testing::UnitTest const& unit_test) override;
+
+    bool failed() const;
+private:
+    std::unique_ptr<testing::TestEventListener> const delegate;
+
+    std::chrono::steady_clock::time_point current_test_start;
+    ::testing::TestInfo const* current_test_info;
+    std::experimental::optional<std::vector<std::string>> current_skip_reasons;
+
+    std::unordered_set<std::string> failed_test_names;
+    std::unordered_set<std::string> skipped_test_names;
+
+    bool failed_{false};
+};
+
+}
+
+#endif //WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_
diff --git a/tests/copy_cut_paste.cpp b/tests/copy_cut_paste.cpp
new file mode 100644 (file)
index 0000000..7e15792
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "data_device.h"
+#include "helpers.h"
+#include "in_process_server.h"
+#include "wl_handle.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+using namespace testing;
+using namespace wlcs;
+
+namespace
+{
+
+auto static const any_width = 100;
+auto static const any_height = 100;
+auto static const any_mime_type = "AnyMimeType";
+
+struct CCnPSource : Client
+{
+    // Can't use "using Client::Client;" because Xenial
+    CCnPSource(Server& server) : Client{server} {}
+
+    Surface const surface{create_visible_surface(any_width, any_height)};
+    WlHandle<wl_data_device_manager> const manager{
+        this->bind_if_supported<wl_data_device_manager>(AnyVersion)};
+    DataDevice data_device{wl_data_device_manager_get_data_device(manager,seat())};
+    DataSource data{wl_data_device_manager_create_data_source(manager)};
+
+    void offer(char const* mime_type)
+    {
+        wl_data_source_offer(data, mime_type);
+        // TODO: collect a serial from the "event that triggered" the selection
+        // (This works while Mir fails to validate the serial)
+        uint32_t const serial = 0;
+        wl_data_device_set_selection(data_device, data, serial);
+        roundtrip();
+    }
+};
+
+struct MockDataDeviceListener : DataDeviceListener
+{
+    using DataDeviceListener::DataDeviceListener;
+
+    MOCK_METHOD2(data_offer, void(struct wl_data_device* wl_data_device, struct wl_data_offer* id));
+};
+
+struct MockDataOfferListener : DataOfferListener
+{
+    using DataOfferListener::DataOfferListener;
+
+    MOCK_METHOD2(offer, void(struct wl_data_offer* data_offer, char const* MimeType));
+};
+
+struct CCnPSink : Client
+{
+    // Can't use "using Client::Client;" because Xenial
+    CCnPSink(Server& server) : Client{server} {}
+
+    WlHandle<wl_data_device_manager> const manager{
+        this->bind_if_supported<wl_data_device_manager>(AnyVersion)};
+    DataDevice sink_data{wl_data_device_manager_get_data_device(manager, seat())};
+    MockDataDeviceListener listener{sink_data};
+
+    Surface create_surface_with_focus()
+    {
+        return Surface{create_visible_surface(any_width, any_height)};
+    }
+};
+
+struct CopyCutPaste : StartedInProcessServer
+{
+    CCnPSource source{the_server()};
+    CCnPSink sink{the_server()};
+
+    MockDataOfferListener mdol;
+
+    void TearDown() override
+    {
+        source.roundtrip();
+        sink.roundtrip();
+        StartedInProcessServer::TearDown();
+    }
+};
+}
+
+TEST_F(CopyCutPaste, given_source_has_offered_when_sink_gets_focus_it_sees_offer)
+{
+    source.offer(any_mime_type);
+
+    EXPECT_CALL(mdol, offer(_, StrEq(any_mime_type)));
+    EXPECT_CALL(sink.listener, data_offer(_,_))
+        .WillOnce(Invoke([&](struct wl_data_device*, struct wl_data_offer* id)
+        { mdol.listen_to(id); }));
+
+    sink.create_surface_with_focus();
+}
+
+TEST_F(CopyCutPaste, given_sink_has_focus_when_source_makes_offer_sink_sees_offer)
+{
+    auto sink_surface_with_focus = sink.create_surface_with_focus();
+
+    EXPECT_CALL(mdol, offer(_, StrEq(any_mime_type)));
+    EXPECT_CALL(sink.listener, data_offer(_,_))
+        .WillOnce(Invoke([&](struct wl_data_device*, struct wl_data_offer* id)
+        { mdol.listen_to(id); }));
+
+    source.offer(any_mime_type);
+}
diff --git a/tests/frame_submission.cpp b/tests/frame_submission.cpp
new file mode 100644 (file)
index 0000000..e938b8e
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using namespace wlcs;
+
+namespace
+{
+
+struct FrameSubmission : StartedInProcessServer
+{
+    Client client{the_server()};
+    Surface surface{client};
+
+    void submit_frame(bool& frame_consumed);
+
+    void wait_for_frame(bool const& frame_consumed);
+};
+
+void FrameSubmission::submit_frame(bool& consumed_flag)
+{
+    static wl_callback_listener const frame_listener
+    {
+        [](void *data, struct wl_callback* callback, uint32_t /*callback_data*/)
+        {
+            *static_cast<bool*>(data) = true;
+            wl_callback_destroy(callback);
+        }
+    };
+
+    consumed_flag = false;
+    wl_callback_add_listener(wl_surface_frame(surface), &frame_listener, &consumed_flag);
+    wl_surface_damage(surface, 0, 0, 200, 200);
+    wl_surface_commit(surface);
+}
+
+void FrameSubmission::wait_for_frame(bool const& consumed_flag)
+{
+    // TODO timeout
+    client.dispatch_until([&consumed_flag]() { return consumed_flag; });
+}
+}
+
+TEST_F(FrameSubmission, post_one_frame_at_a_time)
+{
+    ShmBuffer buffer{client, 200, 200};
+    wl_surface_attach(surface, buffer, 0, 0);
+
+    auto shell_surface = wl_shell_get_shell_surface(client.shell(), surface);
+    wl_shell_surface_set_toplevel(shell_surface);
+
+    for (auto i = 0; i != 10; ++i)
+    {
+        auto frame_consumed = false;
+
+        submit_frame(frame_consumed);
+        wait_for_frame(frame_consumed);
+
+        EXPECT_THAT(frame_consumed, Eq(true));
+    }
+}
diff --git a/tests/gtk_primary_selection.cpp b/tests/gtk_primary_selection.cpp
new file mode 100644 (file)
index 0000000..2dbb5e9
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "gtk_primary_selection.h"
+
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+#include <sys/socket.h>
+
+using namespace wlcs;
+using namespace testing;
+
+namespace
+{
+char const any_mime_type[] = "AnyMimeType";
+char const any_mime_data[] = "AnyMimeData";
+
+struct SourceApp : Client
+{
+    // Can't use "using Client::Client;" because Xenial
+    explicit SourceApp(Server& server) : Client{server} {}
+
+    WlHandle<gtk_primary_selection_device_manager> manager{
+        this->bind_if_supported<gtk_primary_selection_device_manager>(AnyVersion)};
+    GtkPrimarySelectionSource source{manager};
+    GtkPrimarySelectionDevice device{manager, seat()};
+
+    void set_selection()
+    {
+        gtk_primary_selection_device_set_selection(device, source, 0);
+        roundtrip();
+    }
+
+    void offer(char const* mime_type)
+    {
+        gtk_primary_selection_source_offer(source, mime_type);
+        roundtrip();
+    }
+};
+
+struct SinkApp : Client
+{
+    explicit SinkApp(Server& server) : Client{server} { roundtrip(); }
+
+    WlHandle<gtk_primary_selection_device_manager> const manager{
+        this->bind_if_supported<gtk_primary_selection_device_manager>(AnyVersion)};
+    GtkPrimarySelectionDevice device{manager, seat()};
+};
+
+struct GtkPrimarySelection : StartedInProcessServer
+{
+    SourceApp   source_app{the_server()};
+    SinkApp     sink_app{the_server()};
+
+    void TearDown() override
+    {
+        source_app.roundtrip();
+        sink_app.roundtrip();
+        StartedInProcessServer::TearDown();
+    }
+};
+
+struct MockGtkPrimarySelectionDeviceListener : GtkPrimarySelectionDeviceListener
+{
+    using GtkPrimarySelectionDeviceListener::GtkPrimarySelectionDeviceListener;
+
+    MOCK_METHOD2(data_offer, void(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer));
+    MOCK_METHOD2(selection, void(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer));
+};
+
+struct MockGtkPrimarySelectionOfferListener : GtkPrimarySelectionOfferListener
+{
+    using GtkPrimarySelectionOfferListener::GtkPrimarySelectionOfferListener;
+
+    MOCK_METHOD2(offer, void(gtk_primary_selection_offer* offer, const char* mime_type));
+};
+
+struct MockGtkPrimarySelectionSourceListener : GtkPrimarySelectionSourceListener
+{
+    using GtkPrimarySelectionSourceListener::GtkPrimarySelectionSourceListener;
+
+    MOCK_METHOD3(send, void(gtk_primary_selection_source* source, const char* mime_type, int32_t fd));
+
+    MOCK_METHOD1(cancelled, void(gtk_primary_selection_source*));
+};
+
+struct StubGtkPrimarySelectionDeviceListener : GtkPrimarySelectionDeviceListener
+{
+    StubGtkPrimarySelectionDeviceListener(
+        gtk_primary_selection_device* device,
+        GtkPrimarySelectionOfferListener& offer_listener) :
+        GtkPrimarySelectionDeviceListener{device},
+        offer_listener{offer_listener}
+    {
+    }
+
+    void data_offer(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer) override
+    {
+        offer_listener.listen_to(offer);
+        GtkPrimarySelectionDeviceListener::data_offer(device, offer);
+    }
+
+    void selection(gtk_primary_selection_device* device, gtk_primary_selection_offer* offer) override
+    {
+        selected = offer;
+        GtkPrimarySelectionDeviceListener::selection(device, offer);
+    }
+
+    GtkPrimarySelectionOfferListener& offer_listener;
+    gtk_primary_selection_offer* selected = nullptr;
+};
+
+struct StubGtkPrimarySelectionSourceListener : GtkPrimarySelectionSourceListener
+{
+    using GtkPrimarySelectionSourceListener::GtkPrimarySelectionSourceListener;
+
+    void send(gtk_primary_selection_source*, const char*, int32_t fd)
+    {
+        ASSERT_THAT(write(fd, any_mime_data, sizeof any_mime_data), Eq(ssize_t(sizeof any_mime_data)));
+        close(fd);
+    }
+};
+
+struct Pipe
+{
+    int source;
+    int sink;
+
+    Pipe() { socketpair(AF_LOCAL, SOCK_STREAM, 0, &source); }
+    Pipe(Pipe const&) = delete;
+    Pipe& operator=(Pipe const&) = delete;
+
+    ~Pipe() { close(source); close(sink); }
+};
+}
+
+TEST_F(GtkPrimarySelection, source_can_offer)
+{
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+}
+
+TEST_F(GtkPrimarySelection, sink_can_listen)
+{
+    MockGtkPrimarySelectionDeviceListener  device_listener{sink_app.device};
+    MockGtkPrimarySelectionOfferListener   offer_listener;
+
+    InSequence seq;
+    EXPECT_CALL(device_listener, data_offer(_, _))
+        .WillOnce(Invoke([&](auto*, auto* id) { offer_listener.listen_to(id); }));
+
+    EXPECT_CALL(offer_listener, offer(_, StrEq(any_mime_type)));
+
+    EXPECT_CALL(device_listener, selection(_, _));
+
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+
+    sink_app.roundtrip();
+}
+
+TEST_F(GtkPrimarySelection, sink_can_request)
+{
+    GtkPrimarySelectionOfferListener   offer_listener;
+    StubGtkPrimarySelectionDeviceListener  device_listener{sink_app.device, offer_listener};
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+    sink_app.roundtrip();
+    ASSERT_THAT(device_listener.selected, NotNull());
+
+    Pipe pipe;
+    gtk_primary_selection_offer_receive(device_listener.selected, any_mime_type, pipe.source);
+    sink_app.roundtrip();
+}
+
+TEST_F(GtkPrimarySelection, source_sees_request)
+{
+    MockGtkPrimarySelectionSourceListener  source_listener{source_app.source};
+    GtkPrimarySelectionOfferListener   offer_listener;
+    StubGtkPrimarySelectionDeviceListener  device_listener{sink_app.device, offer_listener};
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+    sink_app.roundtrip();
+    ASSERT_THAT(device_listener.selected, NotNull());
+
+    EXPECT_CALL(source_listener, send(_, _, _))
+        .Times(1)
+        .WillRepeatedly(Invoke([&](auto*, auto*, int fd) { close(fd); }));
+
+    Pipe pipe;
+    gtk_primary_selection_offer_receive(device_listener.selected, any_mime_type, pipe.source);
+    sink_app.roundtrip();
+    source_app.roundtrip();
+}
+
+TEST_F(GtkPrimarySelection, source_can_supply_request)
+{
+    StubGtkPrimarySelectionSourceListener source_listener{source_app.source};
+    GtkPrimarySelectionOfferListener   offer_listener;
+    StubGtkPrimarySelectionDeviceListener  device_listener{sink_app.device, offer_listener};
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+    sink_app.roundtrip();
+    ASSERT_THAT(device_listener.selected, NotNull());
+
+    Pipe pipe;
+    gtk_primary_selection_offer_receive(device_listener.selected, any_mime_type, pipe.source);
+    sink_app.roundtrip();
+    source_app.roundtrip();
+
+    char buffer[128];
+    EXPECT_THAT(read(pipe.sink, buffer, sizeof buffer), Eq(ssize_t(sizeof any_mime_data)));
+    EXPECT_THAT(buffer, StrEq(any_mime_data));
+}
diff --git a/tests/pointer_constraints.cpp b/tests/pointer_constraints.cpp
new file mode 100644 (file)
index 0000000..a1cf93f
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "pointer_constraints_unstable_v1.h"
+#include "helpers.h"
+#include "in_process_server.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using namespace wlcs;
+
+namespace
+{
+auto const any_width = 300;
+auto const any_height = 300;
+auto const nw_middle_x = any_width / 2;
+auto const nw_middle_y = any_height / 2;
+auto const se_middle_x = any_width + nw_middle_x;
+auto const se_middle_y = any_height + nw_middle_y;
+
+struct PointerConstraints : StartedInProcessServer
+{
+    // Create a client with two surface (ne & sw) and a cursor centered on the nw_surface
+    Client client{the_server()};
+    Surface se_surface{client.create_visible_surface(any_width, any_height)};
+    Surface nw_surface{client.create_visible_surface(any_width, any_height)};
+    wl_pointer* const pointer = client.the_pointer();
+
+    Pointer cursor = the_server().create_pointer();
+
+    ZwpPointerConstraintsV1 pointer_constraints{client};
+
+    void SetUp() override
+    {
+        StartedInProcessServer::SetUp();
+
+        // Get the surface in a known position with the cursor over it
+        the_server().move_surface_to(nw_surface, 0, 0);
+        cursor.move_to(nw_middle_x, nw_middle_y);
+        the_server().move_surface_to(se_surface, any_width, any_height);
+    }
+
+    void TearDown() override
+    {
+        client.roundtrip();
+        StartedInProcessServer::TearDown();
+    }
+};
+}
+
+TEST_F(PointerConstraints, can_get_locked_pointer)
+{
+    ZwpLockedPointerV1 locked_ptr{pointer_constraints, nw_surface, pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT};
+    EXPECT_THAT(locked_ptr, NotNull());
+}
+
+TEST_F(PointerConstraints, locked_pointer_on_initially_focussed_surface_gets_locked_notification)
+{
+    ZwpLockedPointerV1 locked_ptr{pointer_constraints, nw_surface, pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT};
+
+    EXPECT_CALL(locked_ptr, locked()).Times(1);
+
+    client.roundtrip();
+}
+
+TEST_F(PointerConstraints, locked_pointer_does_not_move)
+{
+    auto const initial_cursor_position = client.pointer_position();
+    ZwpLockedPointerV1 locked_ptr{pointer_constraints, nw_surface, pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT};
+    EXPECT_CALL(locked_ptr, locked()).Times(AnyNumber());
+    client.roundtrip();
+
+    cursor.move_by(10, 10);
+    client.roundtrip();
+
+    EXPECT_THAT(client.pointer_position(), Eq(initial_cursor_position));
+}
+
+TEST_F(PointerConstraints, locked_pointer_on_initially_unfocussed_surface_gets_no_locked_notification)
+{
+    ZwpLockedPointerV1 locked_ptr{pointer_constraints, se_surface, pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT};
+
+    EXPECT_CALL(locked_ptr, locked()).Times(0);
+
+    client.roundtrip();
+}
+
+TEST_F(PointerConstraints, when_cursor_clicks_on_surface_locked_pointer_gets_locked_notification)
+{
+    ZwpLockedPointerV1 locked_ptr{pointer_constraints, se_surface, pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT};
+
+    EXPECT_CALL(locked_ptr, locked()).Times(1);
+
+    cursor.move_to(se_middle_x, se_middle_y);
+    cursor.left_click();
+    client.roundtrip();
+}
+
+TEST_F(PointerConstraints, can_get_confined_pointer)
+{
+    ZwpConfinedPointerV1 confined_ptr{pointer_constraints, nw_surface, pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT};
+    EXPECT_THAT(confined_ptr, NotNull());
+}
diff --git a/tests/primary_selection.cpp b/tests/primary_selection.cpp
new file mode 100644 (file)
index 0000000..720ab3f
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "primary_selection.h"
+
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+#include <sys/socket.h>
+
+using namespace wlcs;
+using namespace testing;
+
+namespace
+{
+char const any_mime_type[] = "AnyMimeType";
+char const any_mime_data[] = "AnyMimeData";
+
+struct SourceApp : Client
+{
+    // Can't use "using Client::Client;" because Xenial
+    explicit SourceApp(Server& server) : Client{server} {}
+
+    WlHandle<zwp_primary_selection_device_manager_v1> const manager{
+        this->bind_if_supported<zwp_primary_selection_device_manager_v1>(AnyVersion)};
+    PrimarySelectionSource source{manager};
+    PrimarySelectionDevice device{manager, seat()};
+
+    void set_selection()
+    {
+        zwp_primary_selection_device_v1_set_selection(device, source, 0);
+        roundtrip();
+    }
+
+    void offer(char const* mime_type)
+    {
+        zwp_primary_selection_source_v1_offer(source, mime_type);
+        roundtrip();
+    }
+};
+
+struct SinkApp : Client
+{
+    explicit SinkApp(Server& server) : Client{server} { roundtrip(); }
+
+    WlHandle<zwp_primary_selection_device_manager_v1> const manager{
+        this->bind_if_supported<zwp_primary_selection_device_manager_v1>(AnyVersion)};
+    PrimarySelectionDevice device{manager, seat()};
+};
+
+struct PrimarySelection : StartedInProcessServer
+{
+    SourceApp   source_app{the_server()};
+    SinkApp     sink_app{the_server()};
+
+    void TearDown() override
+    {
+        source_app.roundtrip();
+        sink_app.roundtrip();
+        StartedInProcessServer::TearDown();
+    }
+};
+
+struct MockPrimarySelectionDeviceListener : PrimarySelectionDeviceListener
+{
+    using PrimarySelectionDeviceListener::PrimarySelectionDeviceListener;
+
+    MOCK_METHOD2(data_offer, void(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer));
+    MOCK_METHOD2(selection, void(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer));
+};
+
+struct MockPrimarySelectionOfferListener : PrimarySelectionOfferListener
+{
+    using PrimarySelectionOfferListener::PrimarySelectionOfferListener;
+
+    MOCK_METHOD2(offer, void(zwp_primary_selection_offer_v1* offer, const char* mime_type));
+};
+
+struct MockPrimarySelectionSourceListener : PrimarySelectionSourceListener
+{
+    using PrimarySelectionSourceListener::PrimarySelectionSourceListener;
+
+    MOCK_METHOD3(send, void(zwp_primary_selection_source_v1* source, const char* mime_type, int32_t fd));
+
+    MOCK_METHOD1(cancelled, void(zwp_primary_selection_source_v1*));
+};
+
+struct StubPrimarySelectionDeviceListener : PrimarySelectionDeviceListener
+{
+    StubPrimarySelectionDeviceListener(
+        zwp_primary_selection_device_v1* device,
+        PrimarySelectionOfferListener& offer_listener) :
+        PrimarySelectionDeviceListener{device},
+        offer_listener{offer_listener}
+    {
+    }
+
+    void data_offer(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer) override
+    {
+        offer_listener.listen_to(offer);
+        PrimarySelectionDeviceListener::data_offer(device, offer);
+    }
+
+    void selection(zwp_primary_selection_device_v1* device, zwp_primary_selection_offer_v1* offer) override
+    {
+        selected = offer;
+        PrimarySelectionDeviceListener::selection(device, offer);
+    }
+
+    PrimarySelectionOfferListener& offer_listener;
+    zwp_primary_selection_offer_v1* selected = nullptr;
+};
+
+struct StubPrimarySelectionSourceListener : PrimarySelectionSourceListener
+{
+    using PrimarySelectionSourceListener::PrimarySelectionSourceListener;
+
+    void send(zwp_primary_selection_source_v1*, const char*, int32_t fd)
+    {
+        ASSERT_THAT(write(fd, any_mime_data, sizeof any_mime_data), Eq(ssize_t(sizeof any_mime_data)));
+        close(fd);
+    }
+};
+
+struct Pipe
+{
+    int source;
+    int sink;
+
+    Pipe() { socketpair(AF_LOCAL, SOCK_STREAM, 0, &source); }
+    Pipe(Pipe const&) = delete;
+    Pipe& operator=(Pipe const&) = delete;
+
+    ~Pipe() { close(source); close(sink); }
+};
+}
+
+TEST_F(PrimarySelection, source_can_offer)
+{
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+}
+
+TEST_F(PrimarySelection, sink_can_listen)
+{
+    MockPrimarySelectionDeviceListener  device_listener{sink_app.device};
+    MockPrimarySelectionOfferListener   offer_listener;
+
+    InSequence seq;
+    EXPECT_CALL(device_listener, data_offer(_, _))
+        .WillOnce(Invoke([&](auto*, auto* id) { offer_listener.listen_to(id); }));
+
+    EXPECT_CALL(offer_listener, offer(_, StrEq(any_mime_type)));
+
+    EXPECT_CALL(device_listener, selection(_, _));
+
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+
+    sink_app.roundtrip();
+}
+
+TEST_F(PrimarySelection, sink_can_request)
+{
+    PrimarySelectionOfferListener   offer_listener;
+    StubPrimarySelectionDeviceListener  device_listener{sink_app.device, offer_listener};
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+    sink_app.roundtrip();
+    ASSERT_THAT(device_listener.selected, NotNull());
+
+    Pipe pipe;
+    zwp_primary_selection_offer_v1_receive(device_listener.selected, any_mime_type, pipe.source);
+    sink_app.roundtrip();
+}
+
+TEST_F(PrimarySelection, source_sees_request)
+{
+    MockPrimarySelectionSourceListener  source_listener{source_app.source};
+    PrimarySelectionOfferListener   offer_listener;
+    StubPrimarySelectionDeviceListener  device_listener{sink_app.device, offer_listener};
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+    sink_app.roundtrip();
+    ASSERT_THAT(device_listener.selected, NotNull());
+
+    EXPECT_CALL(source_listener, send(_, _, _))
+        .Times(1)
+        .WillRepeatedly(Invoke([&](auto*, auto*, int fd) { close(fd); }));
+
+    Pipe pipe;
+    zwp_primary_selection_offer_v1_receive(device_listener.selected, any_mime_type, pipe.source);
+    sink_app.roundtrip();
+    source_app.roundtrip();
+}
+
+TEST_F(PrimarySelection, source_can_supply_request)
+{
+    StubPrimarySelectionSourceListener source_listener{source_app.source};
+    PrimarySelectionOfferListener   offer_listener;
+    StubPrimarySelectionDeviceListener  device_listener{sink_app.device, offer_listener};
+    source_app.offer(any_mime_type);
+    source_app.set_selection();
+    sink_app.roundtrip();
+    ASSERT_THAT(device_listener.selected, NotNull());
+
+    Pipe pipe;
+    zwp_primary_selection_offer_v1_receive(device_listener.selected, any_mime_type, pipe.source);
+    sink_app.roundtrip();
+    source_app.roundtrip();
+
+    char buffer[128];
+    EXPECT_THAT(read(pipe.sink, buffer, sizeof buffer), Eq(ssize_t(sizeof any_mime_data)));
+    EXPECT_THAT(buffer, StrEq(any_mime_data));
+}
diff --git a/tests/relative_pointer.cpp b/tests/relative_pointer.cpp
new file mode 100644 (file)
index 0000000..b2af06a
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright © 2020 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "relative_pointer_unstable_v1.h"
+#include "helpers.h"
+#include "in_process_server.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using namespace wlcs;
+
+namespace
+{
+auto const any_width = 300;
+auto const any_height = 300;
+auto const nw_middle_x = any_width / 2;
+auto const nw_middle_y = any_height / 2;
+
+struct RelativePointer : StartedInProcessServer
+{
+    // Create a client with a surface and a cursor centered on the surface
+    Client a_client{the_server()};
+    Surface a_surface{a_client.create_visible_surface(any_width, any_height)};
+    Pointer cursor = the_server().create_pointer();
+
+    ZwpRelativePointerManagerV1 manager{a_client};
+    ZwpRelativePointerV1 pointer{manager, a_client.the_pointer()};
+
+    void SetUp() override
+    {
+        StartedInProcessServer::SetUp();
+
+        // Get the surface in a known position with the cursor over it
+        the_server().move_surface_to(a_surface, 0, 0);
+        cursor.move_to(nw_middle_x, nw_middle_y);
+    }
+
+    void TearDown() override
+    {
+        a_client.roundtrip();
+        StartedInProcessServer::TearDown();
+    }
+};
+}
+
+TEST_F(RelativePointer, can_get_relative_pointer)
+{
+    EXPECT_THAT(pointer, NotNull());
+}
+
+TEST_F(RelativePointer, relative_pointer_gets_movement)
+{
+    auto const move_x = any_width/6;
+    auto const move_y = any_height/6;
+
+    EXPECT_CALL(pointer, relative_motion(_, _,
+                                         wl_fixed_from_int(move_x), wl_fixed_from_int(move_y),
+                                         wl_fixed_from_int(move_x), wl_fixed_from_int(move_y))).Times(1);
+
+    cursor.move_by(move_x, move_y);
+}
diff --git a/tests/self_test.cpp b/tests/self_test.cpp
new file mode 100644 (file)
index 0000000..e5428e8
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
+ */
+
+#include "data_device.h"
+#include "helpers.h"
+#include "gtest_helpers.h"
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+using namespace testing;
+using namespace wlcs;
+
+namespace
+{
+
+struct SelfTest : StartedInProcessServer
+{
+    Client client1{the_server()};
+};
+
+auto static const any_width = 100;
+auto static const any_height = 100;
+}
+
+TEST_F(SelfTest, when_creating_second_client_nothing_bad_happens)
+{
+    Client client2{the_server()};
+}
+
+TEST_F(SelfTest, given_second_client_when_roundtripping_first_client_nothing_bad_happens)
+{
+    Client client2{the_server()};
+    client1.roundtrip();
+}
+
+TEST_F(SelfTest, given_second_client_when_roundtripping_both_clients_nothing_bad_happens)
+{
+    Client client2{the_server()};
+
+    for (auto i = 0; i != 10; ++i)
+    {
+        client1.roundtrip();
+        client2.roundtrip();
+    }
+}
+
+TEST_F(SelfTest, when_a_client_creates_a_surface_nothing_bad_happens)
+{
+    Surface const surface1{client1.create_visible_surface(any_width, any_height)};
+    client1.roundtrip();
+}
+
+TEST_F(SelfTest, given_second_client_when_first_creates_a_surface_nothing_bad_happens)
+{
+    Client client2{the_server()};
+
+    Surface const surface1{client1.create_visible_surface(any_width, any_height)};
+
+    for (auto i = 0; i != 10; ++i)
+    {
+        client1.roundtrip();
+        client2.roundtrip();
+    }
+}
+
+TEST_F(SelfTest, given_second_client_when_both_create_a_surface_nothing_bad_happens)
+{
+    Client client2{the_server()};
+
+    Surface const surface1{client1.create_visible_surface(any_width, any_height)};
+
+    Surface const surface2{client2.create_visible_surface(any_width, any_height)};
+
+    for (auto i = 0; i != 10; ++i)
+    {
+        client1.roundtrip();
+        client2.roundtrip();
+    }
+}
+
+TEST_F(SelfTest, xfail_failure_is_noted)
+{
+    ::testing::Test::RecordProperty("wlcs-skip-test", "Reason goes here");
+
+    FAIL() << "This message shouldn't be seen";
+}
+
+TEST_F(SelfTest, expected_missing_extension_is_xfail)
+{
+    throw wlcs::ExtensionExpectedlyNotSupported("xdg_not_really_an_extension", wlcs::AtLeastVersion{1});
+}
+
+TEST_F(SelfTest, acquiring_unsupported_extension_is_xfail)
+{
+    auto const extension_list = the_server().supported_extensions();
+
+    if (!extension_list)
+    {
+        ::testing::Test::RecordProperty("wlcs-skip-test", "Compositor Integration module is too old for expected extension failures");
+        FAIL() << "Requires unsupported feature from module under test";
+    }
+
+    Client client{the_server()};
+
+    wl_interface unsupported_interface = wl_shell_interface;
+    unsupported_interface.name = "wlcs_non_existent_extension";
+    client.bind_if_supported(unsupported_interface, AnyVersion);
+
+    FAIL() << "We should have (x)failed at acquiring the interface";
+}
+
+TEST_F(SelfTest, acquiring_unsupported_extension_version_is_xfail)
+{
+    auto const extension_list = the_server().supported_extensions();
+
+    if (!extension_list)
+    {
+        ::testing::Test::RecordProperty("wlcs-skip-test", "Compositor Integration module is too old for expected extension failures");
+        FAIL() << "Requires unsupported feature from module under test";
+    }
+
+    Client client{the_server()};
+
+    client.bind_if_supported(wl_shell_interface, AtLeastVersion{wl_shell_interface.version + 1u});
+
+    FAIL() << "We should have (x)failed at acquiring the interface";
+}
+
+TEST_F(SelfTest, dispatch_until_times_out_on_failure)
+{
+    Client client{the_server()};
+
+    // Ensure that there's some events happening on the Wayland socket
+    auto dummy = client.create_visible_surface(300, 300);
+    dummy.attach_buffer(300, 300);
+    wl_surface_commit(dummy);
+
+    try
+    {
+        client.dispatch_until([]() { return false; }, std::chrono::seconds{1});
+    }
+    catch (wlcs::Timeout const&)
+    {
+        return;
+    }
+    FAIL() << "Dispatch did not raise a wlcs::Timeout exception";
+}
+
+TEST_F(SelfTest, dispatch_until_times_out_at_the_right_time)
+{
+    using namespace std::literals::chrono_literals;
+
+    Client client{the_server()};
+
+    auto const timeout = 5s;
+    auto const expected_end = std::chrono::steady_clock::now() + timeout;
+    try
+    {
+        client.dispatch_until([]() { return false; }, timeout);
+    }
+    catch (wlcs::Timeout const&)
+    {
+        EXPECT_THAT(std::chrono::steady_clock::now(), Gt(expected_end));
+        EXPECT_THAT(std::chrono::steady_clock::now(), Lt(expected_end + 5s));
+        return;
+    }
+    FAIL() << "Dispatch did not raise a wlcs::Timeout exception";
+}
diff --git a/tests/subsurfaces.cpp b/tests/subsurfaces.cpp
new file mode 100644 (file)
index 0000000..2343e11
--- /dev/null
@@ -0,0 +1,936 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_v6.h"
+
+#include <gmock/gmock.h>
+
+#include <vector>
+
+using namespace testing;
+
+struct AbstractInputDevice
+{
+    virtual void to_screen_position(int x, int y) = 0;
+    virtual wl_surface* focused_window() = 0;
+    virtual std::pair<int, int> position_on_window() = 0;
+    virtual ~AbstractInputDevice() = default;
+};
+
+struct PointerInputDevice : AbstractInputDevice
+{
+    PointerInputDevice(wlcs::Server& server, wlcs::Client& client):
+        client{client},
+        pointer{server.create_pointer()}
+    {
+    }
+
+    void to_screen_position(int x, int y) override
+    {
+        pointer.move_to(0, 0);
+        pointer.move_to(x, y);
+    }
+
+    wl_surface* focused_window() override
+    {
+        return client.window_under_cursor();
+    }
+
+    std::pair<wl_fixed_t, wl_fixed_t> position_on_window() override
+    {
+        return client.pointer_position();
+    }
+
+    wlcs::Client& client;
+    wlcs::Pointer pointer;
+};
+
+struct TouchInputDevice : AbstractInputDevice
+{
+    TouchInputDevice(wlcs::Server& server, wlcs::Client& client):
+        client{client},
+        touch{server.create_touch()}
+    {
+    }
+
+    void to_screen_position(int x, int y) override
+    {
+        touch.up();
+        touch.down_at(x, y);
+    }
+
+    wl_surface* focused_window() override
+    {
+        return client.touched_window();
+    }
+
+    std::pair<wl_fixed_t, wl_fixed_t> position_on_window() override
+    {
+        return client.touch_position();
+    }
+
+    wlcs::Client& client;
+    wlcs::Touch touch;
+};
+
+struct SubsurfaceTestParams
+{
+    std::string name;
+    std::function<std::unique_ptr<wlcs::Surface>(wlcs::InProcessServer& server,
+                                                 wlcs::Client& client,
+                                                 int x, int y,
+                                                 int width, int height)> make_surface;
+    std::function<std::unique_ptr<AbstractInputDevice>(wlcs::Server& server,
+                                                       wlcs::Client& client)> make_input_device;
+};
+
+std::ostream& operator<<(std::ostream& out, SubsurfaceTestParams const& param)
+{
+    return out << param.name;
+}
+
+class SubsurfaceTest :
+    public wlcs::StartedInProcessServer,
+    public testing::WithParamInterface<SubsurfaceTestParams>
+{
+public:
+    static int const surface_width = 200, surface_height = 300;
+    static int const subsurface_width = 50, subsurface_height = 50;
+    static int const surface_x = 20, surface_y = 30;
+
+    SubsurfaceTest():
+        client{the_server()},
+        main_surface{std::move(*GetParam().make_surface(
+            *this, client, surface_x, surface_y, surface_width, surface_height))},
+        subsurface{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)},
+        input_device{GetParam().make_input_device(the_server(), client)}
+    {
+        client.roundtrip();
+    }
+
+    void move_subsurface_to(int x, int y)
+    {
+        wl_subsurface_set_position(subsurface, x, y);
+        wl_surface_commit(main_surface);
+        client.roundtrip();
+    }
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface main_surface;
+    wlcs::Subsurface subsurface;
+    std::unique_ptr<AbstractInputDevice> input_device;
+};
+
+class SubsurfaceMultilevelTest :
+    public wlcs::StartedInProcessServer,
+    public testing::WithParamInterface<SubsurfaceTestParams>
+{
+public:
+    static int const surface_width = 200, surface_height = 300;
+    static int const subsurface_width = 50, subsurface_height = 50;
+    static int const surface_x = 20, surface_y = 30;
+
+    SubsurfaceMultilevelTest():
+        client{the_server()},
+        main_surface{std::move(*GetParam().make_surface(
+            *this, client, surface_x, surface_y, surface_width, surface_height))},
+        parent_subsurface{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)},
+        child_subsurface{wlcs::Subsurface::create_visible(parent_subsurface, 0, 0, subsurface_width, subsurface_height)},
+        input_device{GetParam().make_input_device(the_server(), client)}
+    {
+        client.roundtrip();
+    }
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface main_surface;
+    wlcs::Subsurface parent_subsurface;
+    wlcs::Subsurface child_subsurface;
+    std::unique_ptr<AbstractInputDevice> input_device;
+};
+
+TEST_P(SubsurfaceTest, subsurface_has_correct_parent)
+{
+    EXPECT_THAT(&subsurface.parent(), Eq(&main_surface));
+}
+
+TEST_P(SubsurfaceTest, subsurface_gets_pointer_input)
+{
+    int const pointer_x = surface_x + 10, pointer_y = surface_y + 5;
+
+    input_device->to_screen_position(pointer_x, pointer_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface";
+    EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface));
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - surface_x),
+                    wl_fixed_from_int(pointer_y - surface_y))));
+}
+
+TEST_P(SubsurfaceTest, pointer_input_correctly_offset_for_subsurface)
+{
+    int const pointer_x = surface_x + 13, pointer_y = surface_y + 24;
+    int const subsurface_x = 8, subsurface_y = 17;
+
+    move_subsurface_to(subsurface_x, subsurface_y);
+
+    input_device->to_screen_position(pointer_x, pointer_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface";
+    EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface));
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - surface_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - surface_y - subsurface_y))));
+}
+
+TEST_P(SubsurfaceTest, sync_subsurface_moves_when_only_parent_committed)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+    // Position is applied when parent (main_surface) commits, so subsurface does not need to be committed
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))))
+        << "Subsurface did not move after parent commit";
+}
+
+TEST_P(SubsurfaceTest, desync_subsurface_moves_when_only_parent_committed)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_desync(subsurface);
+
+    wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+    // Position is applied when parent (main_surface) commits, so subsurface does not need to be committed
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))))
+        << "Subsurface did not move after parent commit";
+}
+
+TEST_P(SubsurfaceTest, subsurface_does_not_move_when_parent_not_committed)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_desync(subsurface);
+
+    wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+    wl_surface_commit(subsurface);
+    // We don't call wl_surface_commit(main_surface), so position should not be applied
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))))
+        << "Subsurface moved to new location without parent being committed";
+}
+
+TEST_P(SubsurfaceTest, subsurface_extends_parent_input_region)
+{
+    int const pointer_x = surface_x - 5, pointer_y = surface_y + surface_height + 8;
+    int const subsurface_x = -10, subsurface_y = surface_height - 10;
+
+    move_subsurface_to(subsurface_x, subsurface_y);
+
+    input_device->to_screen_position(pointer_x, pointer_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface";
+    EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface));
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - surface_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - surface_y - subsurface_y))));
+}
+
+TEST_P(SubsurfaceTest, input_falls_through_empty_subsurface_input_region)
+{
+    int const pointer_x = surface_x + 10, pointer_y = surface_y + 5;
+
+    auto const wl_region = wl_compositor_create_region(client.compositor());
+    wl_surface_set_input_region(subsurface, wl_region);
+    wl_region_destroy(wl_region);
+    wl_surface_commit(subsurface);
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x, pointer_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface)) << "input was incorrectly caught by subsurface";
+    EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface));
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - surface_x),
+                    wl_fixed_from_int(pointer_y - surface_y))));
+}
+
+TEST_P(SubsurfaceTest, gets_input_over_surface_with_empty_region)
+{
+    int const pointer_x = surface_x + 32, pointer_y = surface_y + 21;
+
+    auto const wl_region = wl_compositor_create_region(client.compositor());
+    wl_surface_set_input_region(main_surface, wl_region);
+    wl_region_destroy(wl_region);
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x, pointer_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->focused_window(), Ne((wl_surface*)main_surface)) << "input fell through to main surface";
+    EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface));
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - surface_x),
+                    wl_fixed_from_int(pointer_y - surface_y))));
+}
+
+TEST_P(SubsurfaceTest, one_subsurface_to_another_fallthrough)
+{
+    int const pointer_x_0 = 3, pointer_y_0 = 3;
+    int const pointer_x_1 = 3, pointer_y_1 = 10;
+    int const pointer_x_2 = 10, pointer_y_2 = 3;
+    int const subsurface_x = 0, subsurface_y = 5;
+    int const subsurface_top_x = 5, subsurface_top_y = 0;
+    move_subsurface_to(subsurface_x, subsurface_y);
+    auto subsurface_top{wlcs::Subsurface::create_visible(main_surface, subsurface_top_x, subsurface_top_y, subsurface_width, subsurface_height)};
+
+    input_device->to_screen_position(pointer_x_0 + surface_x, pointer_y_0 + surface_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)) << "main surface not focused";
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x_0),
+                    wl_fixed_from_int(pointer_y_0))));
+
+    input_device->to_screen_position(pointer_x_1 + surface_x, pointer_y_1 + surface_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "lower subsurface not focused";
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x_1 - subsurface_x),
+                    wl_fixed_from_int(pointer_y_1 - subsurface_y))));
+
+    input_device->to_screen_position(pointer_x_2 + surface_x, pointer_y_2 + surface_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface_top)) << "upper subsurface not focused";
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x_2 - subsurface_top_x),
+                    wl_fixed_from_int(pointer_y_2 - subsurface_top_y))));
+}
+
+TEST_P(SubsurfaceTest, place_below_simple)
+{
+    auto subsurface_moving_down{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)};
+    wl_subsurface_place_below(subsurface_moving_down, subsurface);
+    wl_surface_commit(subsurface_moving_down);
+    wl_surface_commit(subsurface);
+    wl_surface_commit(main_surface);
+
+    input_device->to_screen_position(5 + surface_x, 5 + surface_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface_moving_down))
+        << "subsurface.place_below() did not have an effect";
+
+    ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface))
+        << "wrong surface/subsurface on top";
+}
+
+TEST_P(SubsurfaceTest, place_above_simple)
+{
+    auto subsurface_being_covered{wlcs::Subsurface::create_visible(main_surface, 0, 0, subsurface_width, subsurface_height)};
+    wl_subsurface_place_above(subsurface, subsurface_being_covered);
+    wl_surface_commit(subsurface);
+    wl_surface_commit(subsurface_being_covered);
+    wl_surface_commit(main_surface);
+
+    input_device->to_screen_position(5 + surface_x, 5 + surface_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface_being_covered))
+        << "subsurface.place_above() did not have an effect";
+
+    ASSERT_THAT(input_device->focused_window(), Ne((wl_surface*)subsurface))
+        << "wrong surface/subsurface on top";
+}
+
+TEST_P(SubsurfaceTest, subsurface_of_a_subsurface_handled)
+{
+    int const pointer_x_0 = 3, pointer_y_0 = 3;
+    int const pointer_x_1 = 3, pointer_y_1 = 10;
+    int const pointer_x_2 = 10, pointer_y_2 = 3;
+    int const subsurface_x = 0, subsurface_y = 5;
+    int const subsurface_top_x = 5, subsurface_top_y = -5;
+    move_subsurface_to(subsurface_x, subsurface_y);
+    auto subsurface_top{wlcs::Subsurface::create_visible(subsurface, subsurface_top_x, subsurface_top_y, subsurface_width, subsurface_height)};
+
+    input_device->to_screen_position(pointer_x_0 + surface_x, pointer_y_0 + surface_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)) << "main surface not focused";
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x_0),
+                    wl_fixed_from_int(pointer_y_0))));
+
+    input_device->to_screen_position(pointer_x_1 + surface_x, pointer_y_1 + surface_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "lower subsurface not focused";
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x_1 - subsurface_x),
+                    wl_fixed_from_int(pointer_y_1 - subsurface_y))));
+
+    input_device->to_screen_position(pointer_x_2 + surface_x, pointer_y_2 + surface_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface_top)) << "subsurface of subsurface not focused";
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x_2 - subsurface_top_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y_2 - subsurface_top_y - subsurface_y))));
+}
+
+TEST_P(SubsurfaceTest, subsurface_moves_under_input_device_once)
+{
+    int const input_x = surface_x + 10, input_y = surface_y + 5;
+    int const subsurface_x = -23, subsurface_y = -17;
+
+    input_device->to_screen_position(input_x, input_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "precondition failed";
+    ASSERT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(input_x - surface_x),
+                    wl_fixed_from_int(input_y - surface_y)))) << "precondition failed";
+
+    wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+    wl_surface_commit(subsurface);
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "subsurface not focuesed";
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(input_x - surface_x),
+                    wl_fixed_from_int(input_y - surface_y)))) << "input device did not get new location";
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(input_x - surface_x - subsurface_x),
+                    wl_fixed_from_int(input_y - surface_y - subsurface_y)))) << "input device in wrong location";
+}
+
+TEST_P(SubsurfaceTest, subsurface_moves_under_input_device_twice)
+{
+    int const input_x = surface_x + 10, input_y = surface_y + 5;
+    int const subsurface_x_0 = 4, subsurface_y_0 = 2;
+    int const subsurface_x_1 = -23, subsurface_y_1 = -17;
+
+    input_device->to_screen_position(input_x, input_y);
+    wl_subsurface_set_position(subsurface, subsurface_x_0, subsurface_y_0);
+    wl_surface_commit(subsurface);
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "precondition failed";
+    ASSERT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(input_x - surface_x - subsurface_x_0),
+                    wl_fixed_from_int(input_y - surface_y - subsurface_y_0)))) << "precondition failed";
+
+    wl_subsurface_set_position(subsurface, subsurface_x_1, subsurface_y_1);
+    wl_surface_commit(subsurface);
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "subsurface not focuesed";
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(input_x - surface_x - subsurface_x_0),
+                    wl_fixed_from_int(input_y - surface_y - subsurface_y_0)))) << "input device did not get new location";
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(input_x - surface_x - subsurface_x_1),
+                    wl_fixed_from_int(input_y - surface_y - subsurface_y_1)))) << "input device in wrong location";
+}
+
+TEST_P(SubsurfaceTest, subsurface_moves_out_from_under_input_device)
+{
+    int const input_x = surface_x + 10, input_y = surface_y + 5;
+    int const subsurface_x = input_x - surface_x + 10, subsurface_y = input_y - surface_y + 10;
+
+    input_device->to_screen_position(input_x, input_y);
+    client.roundtrip();
+
+    ASSERT_THAT(input_device->focused_window(), Eq((wl_surface*)subsurface)) << "precondition failed";
+    ASSERT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(input_x - surface_x),
+                    wl_fixed_from_int(input_y - surface_y)))) << "precondition failed";
+
+    wl_subsurface_set_position(subsurface, subsurface_x, subsurface_y);
+    wl_surface_commit(subsurface);
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->focused_window(), Eq((wl_surface*)main_surface)) << "main surface not focuesed";
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(input_x - surface_x),
+                    wl_fixed_from_int(input_y - surface_y)))) << "input device in wrong location";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    WlShellSubsurfaces,
+    SubsurfaceTest,
+    testing::Values(
+        SubsurfaceTestParams{
+            "wl_shell_surface",
+            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+                -> std::unique_ptr<wlcs::Surface>
+                {
+                    auto surface = client.create_wl_shell_surface(
+                        width,
+                        height);
+                    server.the_server().move_surface_to(surface, x, y);
+                    return std::make_unique<wlcs::Surface>(std::move(surface));
+                },
+            [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+                {
+                    return std::make_unique<PointerInputDevice>(server, client);
+                }
+        }
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    XdgShellV6Subsurfaces,
+    SubsurfaceTest,
+    testing::Values(
+        SubsurfaceTestParams{
+            "xdg_v6_surface",
+            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+                -> std::unique_ptr<wlcs::Surface>
+                {
+                    auto surface = client.create_xdg_shell_v6_surface(
+                        width,
+                        height);
+                    server.the_server().move_surface_to(surface, x, y);
+                    return std::make_unique<wlcs::Surface>(std::move(surface));
+                },
+            [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+                {
+                    return std::make_unique<PointerInputDevice>(server, client);
+                }
+        }
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    XdgShellStableSubsurfaces,
+    SubsurfaceTest,
+    testing::Values(
+        SubsurfaceTestParams{
+            "xdg_stable_surface",
+            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+                -> std::unique_ptr<wlcs::Surface>
+                {
+                    auto surface = client.create_xdg_shell_stable_surface(
+                        width,
+                        height);
+                    server.the_server().move_surface_to(surface, x, y);
+                    return std::make_unique<wlcs::Surface>(std::move(surface));
+                },
+            [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+                {
+                    return std::make_unique<PointerInputDevice>(server, client);
+                }
+        }
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    TouchInputSubsurfaces,
+    SubsurfaceTest,
+    testing::Values(
+        SubsurfaceTestParams{
+            "touch_input_subsurfaces",
+            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+                -> std::unique_ptr<wlcs::Surface>
+                {
+                    auto surface = client.create_xdg_shell_v6_surface(
+                        width,
+                        height);
+                    server.the_server().move_surface_to(surface, x, y);
+                    return std::make_unique<wlcs::Surface>(std::move(surface));
+                },
+            [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+                {
+                    return std::make_unique<TouchInputDevice>(server, client);
+                }
+        }
+    ));
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_with_sync_parent_does_not_move_when_only_grandparent_committed)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+    // We don't call wl_surface_commit(parent_subsurface), so position should not be applied
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))))
+        << "Subsurface moved without parent being committed";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_with_desync_parent_does_not_move_when_only_grandparent_committed)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_desync(parent_subsurface);
+
+    wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+    // We don't call wl_surface_commit(parent_subsurface), so position should not be applied
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))))
+        << "Subsurface moved without parent being committed";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_with_sync_parent_does_not_move_when_only_parent_committed)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+    wl_surface_commit(parent_subsurface);
+    // We don't call wl_surface_commit(main_surface), which should be required before position is applied because
+    // parent_subsurface is sync
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))))
+        << "Subsurface of sync parent moved without grandparent being committed";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_with_desync_parent_moves_when_only_parent_committed)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_desync(parent_subsurface);
+
+    wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+    wl_surface_commit(parent_subsurface);
+    // wl_surface_commit(main_surface) should NOT be required for position to be applied because parent_subsurface is
+    // desync
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))))
+        << "Did not move on desync parent commit";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_does_not_move_when_grandparent_commit_is_before_sync_parent_commit)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+    wl_surface_commit(main_surface);
+    wl_surface_commit(parent_subsurface);
+    // Committing main_surface would need to happend AFTER parent_subsurface in order for position to be applied
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))))
+        << "Subsurface moved when hierarchy commits were in the wrong order";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_moves_after_both_sync_parent_and_grandparent_commit)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_y);
+    wl_surface_commit(parent_subsurface);
+    wl_surface_commit(main_surface);
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))))
+        << "Did not move after parent and grandparent both comitted";
+}
+
+TEST_P(SubsurfaceMultilevelTest, by_default_subsurface_is_sync)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_x);
+    wl_surface_commit(parent_subsurface);
+    // Not calling wl_surface_commit(main_surface), so new position should not be applied
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))))
+        << "Subsurface moved without parent commit (it should have been 'sync' by default, but is acting as desync)";
+}
+
+TEST_P(SubsurfaceMultilevelTest, subsurface_can_be_set_to_sync)
+{
+    int const pointer_x = 30, pointer_y = 30;
+    int const subsurface_x = 20, subsurface_y = 20;
+
+    wl_subsurface_set_desync(child_subsurface);
+    wl_subsurface_set_sync(child_subsurface);
+
+    wl_subsurface_set_position(child_subsurface, subsurface_x, subsurface_x);
+    wl_surface_commit(parent_subsurface);
+    // Not calling wl_surface_commit(main_surface), so new position should not be applied
+    client.roundtrip();
+
+    input_device->to_screen_position(pointer_x + surface_x, pointer_y + surface_y);
+    client.roundtrip();
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x),
+                    wl_fixed_from_int(pointer_y))));
+
+    EXPECT_THAT(input_device->position_on_window(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - subsurface_x),
+                    wl_fixed_from_int(pointer_y - subsurface_y))))
+        << "Subsurface moved without parent commit (it should have been 'sync' by default, but is acting as desync)";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    WlShellSubsurfaces,
+    SubsurfaceMultilevelTest,
+    testing::Values(
+        SubsurfaceTestParams{
+            "wl_shell_surface",
+            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+                -> std::unique_ptr<wlcs::Surface>
+                {
+                    auto surface = client.create_wl_shell_surface(
+                        width,
+                        height);
+                    server.the_server().move_surface_to(surface, x, y);
+                    return std::make_unique<wlcs::Surface>(std::move(surface));
+                },
+            [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+                {
+                    return std::make_unique<PointerInputDevice>(server, client);
+                }
+        }
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    XdgShellV6Subsurfaces,
+    SubsurfaceMultilevelTest,
+    testing::Values(
+        SubsurfaceTestParams{
+            "xdg_v6_surface",
+            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+                -> std::unique_ptr<wlcs::Surface>
+                {
+                    auto surface = client.create_xdg_shell_v6_surface(
+                        width,
+                        height);
+                    server.the_server().move_surface_to(surface, x, y);
+                    return std::make_unique<wlcs::Surface>(std::move(surface));
+                },
+            [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+                {
+                    return std::make_unique<PointerInputDevice>(server, client);
+                }
+        }
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    XdgShellStableSubsurfaces,
+    SubsurfaceMultilevelTest,
+    testing::Values(
+        SubsurfaceTestParams{
+            "xdg_stable_surface",
+            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+                -> std::unique_ptr<wlcs::Surface>
+                {
+                    auto surface = client.create_xdg_shell_stable_surface(
+                        width,
+                        height);
+                    server.the_server().move_surface_to(surface, x, y);
+                    return std::make_unique<wlcs::Surface>(std::move(surface));
+                },
+            [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+                {
+                    return std::make_unique<PointerInputDevice>(server, client);
+                }
+        }
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    TouchInputSubsurfaces,
+    SubsurfaceMultilevelTest,
+    testing::Values(
+        SubsurfaceTestParams{
+            "touch_input_subsurfaces",
+            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
+                -> std::unique_ptr<wlcs::Surface>
+                {
+                    auto surface = client.create_xdg_shell_v6_surface(
+                        width,
+                        height);
+                    server.the_server().move_surface_to(surface, x, y);
+                    return std::make_unique<wlcs::Surface>(std::move(surface));
+                },
+            [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr<AbstractInputDevice>
+                {
+                    return std::make_unique<TouchInputDevice>(server, client);
+                }
+        }
+    ));
+
+// TODO: combinations of sync and desync at various levels of the tree
+// TODO: "bad_surface" error
+// TODO: seitch to the new surface/input method abstraction
diff --git a/tests/surface_input_regions.cpp b/tests/surface_input_regions.cpp
new file mode 100644 (file)
index 0000000..ce76052
--- /dev/null
@@ -0,0 +1,837 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "surface_builder.h"
+#include "input_method.h"
+
+#include <gmock/gmock.h>
+
+#include <vector>
+#include <memory>
+#include <experimental/optional>
+
+using namespace testing;
+
+enum class RegionAction
+{
+    add,
+    subtract,
+};
+
+struct Region
+{
+    struct Element
+    {
+        RegionAction action;
+        std::pair<int, int> top_left;
+        std::pair<int, int> size;
+    };
+
+    std::string name;
+    std::pair<int, int> surface_size;
+    std::vector<Element> elements;
+
+    void apply_to_surface(wlcs::Client& client, wl_surface* surface) const
+    {
+        if (elements.empty())
+            return;
+        auto const wl_region = wl_compositor_create_region(client.compositor());
+        for (auto const& e: elements)
+        {
+            switch(e.action)
+            {
+            case RegionAction::add:
+                wl_region_add(wl_region, e.top_left.first, e.top_left.second, e.size.first, e.size.second);
+                break;
+            case RegionAction::subtract:
+                wl_region_subtract(wl_region, e.top_left.first, e.top_left.second, e.size.first, e.size.second);
+                break;
+            }
+        }
+        wl_surface_set_input_region(surface, wl_region);
+        wl_region_destroy(wl_region);
+        wl_surface_commit(surface);
+        client.roundtrip();
+    }
+};
+
+struct RegionWithTestPoints
+{
+    RegionWithTestPoints()
+        : name{"default constructed"},
+          region{"default constructed", {0, 0}, {}},
+          on_surface{0, 0},
+          off_surface{0, 0}
+    {
+    }
+
+    RegionWithTestPoints(
+        std::string const& name,
+        Region region,
+        std::pair<int, int> on_surface,
+        std::pair<int, int> delta)
+        : name{name},
+          region{region},
+          on_surface{on_surface},
+          off_surface{on_surface.first + delta.first, on_surface.second + delta.second}
+    {
+    }
+
+    std::string name;
+    Region region;
+    std::pair<int, int> on_surface;
+    std::pair<int, int> off_surface;
+};
+
+std::ostream& operator<<(std::ostream& out, RegionWithTestPoints const& param)
+{
+    return out << param.region.name << " " << param.name;
+}
+
+auto const all_surface_types = ValuesIn(wlcs::SurfaceBuilder::all_surface_types());
+auto const toplevel_surface_types = ValuesIn(wlcs::SurfaceBuilder::toplevel_surface_types());
+auto const xdg_stable_surface_type = Values(
+    std::static_pointer_cast<wlcs::SurfaceBuilder>(std::make_shared<wlcs::XdgStableSurfaceBuilder>(0, 0, 0, 0)));
+
+auto const all_input_types = ValuesIn(wlcs::InputMethod::all_input_methods());
+
+class RegionSurfaceInputCombinations :
+    public wlcs::InProcessServer,
+    public testing::WithParamInterface<std::tuple<
+        RegionWithTestPoints,
+        std::shared_ptr<wlcs::SurfaceBuilder>,
+        std::shared_ptr<wlcs::InputMethod>>>
+{
+};
+
+auto const surface_size{std::make_pair(215, 108)};
+
+Region const default_region{"default", surface_size, {}};
+
+auto const default_edges = Values(
+    RegionWithTestPoints{"left edge", default_region,
+        {0, surface_size.second / 2},
+        {-1, 0}},
+    RegionWithTestPoints{"bottom edge", default_region,
+        {surface_size.first / 2, surface_size.second - 1},
+        {0, 1}},
+    RegionWithTestPoints{"right edge", default_region,
+        {surface_size.first - 1, surface_size.second / 2},
+        {1, 0}},
+    RegionWithTestPoints{"top edge", default_region,
+        {surface_size.first / 2, 0},
+        {0, -1}});
+
+Region const full_surface_region{"explicitly specified full surface", surface_size, {
+    {RegionAction::add, {0, 0}, surface_size}}};
+
+auto const full_surface_edges = Values(
+    RegionWithTestPoints{"left edge", full_surface_region,
+        {0, surface_size.second / 2},
+        {-1, 0}},
+    RegionWithTestPoints{"bottom edge", full_surface_region,
+        {surface_size.first / 2, surface_size.second - 1},
+        {0, 1}},
+    RegionWithTestPoints{"right edge", full_surface_region,
+        {surface_size.first - 1, surface_size.second / 2},
+        {1, 0}},
+    RegionWithTestPoints{"top edge", full_surface_region,
+        {surface_size.first / 2, 0},
+        {0, -1}});
+
+auto const region_inset = std::make_pair(12, 17);
+
+Region const smaller_region{"smaller", surface_size, {
+    {RegionAction::add, region_inset, {
+        surface_size.first - region_inset.first * 2,
+        surface_size.second - region_inset.second * 2}}}};
+
+auto const smaller_region_edges = Values(
+    RegionWithTestPoints{"left edge", smaller_region,
+        {region_inset.first, surface_size.second / 2},
+        {-1, 0}},
+    RegionWithTestPoints{"bottom edge", smaller_region,
+        {surface_size.first / 2, surface_size.second - region_inset.second - 1},
+        {0, 1}},
+    RegionWithTestPoints{"right edge", smaller_region,
+        {surface_size.first - region_inset.first - 1, surface_size.second / 2},
+        {1, 0}},
+    RegionWithTestPoints{"top edge", smaller_region,
+        {surface_size.first / 2, region_inset.second},
+        {0, -1}});
+
+// If a region is larger then the surface it should be clipped
+
+auto const region_outset = std::make_pair(12, 17);
+
+Region const larger_region{"larger", surface_size, {
+    {RegionAction::add, {-region_outset.first, -region_outset.second}, {
+        surface_size.first + region_inset.first * 2,
+        surface_size.second + region_inset.second * 2}}}};
+
+auto const larger_region_edges = Values(
+    RegionWithTestPoints{"left edge", larger_region,
+        {0, surface_size.second / 2},
+        {-1, 0}},
+    RegionWithTestPoints{"bottom edge", larger_region,
+        {surface_size.first / 2, surface_size.second - 1},
+        {0, 1}},
+    RegionWithTestPoints{"right edge", larger_region,
+        {surface_size.first - 1, surface_size.second / 2},
+        {1, 0}},
+    RegionWithTestPoints{"top edge", larger_region,
+        {surface_size.first / 2, 0},
+        {0, -1}});
+
+int const small_rect_inset = 16;
+
+// Looks something like this:
+// (dotted line is real surface, solid line is input region rectangles)
+//   _______A_______
+//  |               |
+// B|               |C
+//  |_D___________E_|
+//  :   |       |   :
+//  :  F|       |G  :
+//  '---|---H---|---'
+//      |_______|
+//          I
+Region const multi_rect_region{"multi-rect", surface_size, {
+    // upper rect
+    {RegionAction::add,
+        {0, 0},
+        {surface_size.first, surface_size.second / 2}},
+    // lower rect
+    {RegionAction::add,
+        {small_rect_inset, surface_size.second / 2},
+        {surface_size.first - small_rect_inset * 2, surface_size.second / 2 + 20}}}};
+
+auto const multi_rect_edges = Values(
+    RegionWithTestPoints{"top region edge at surface top edge", multi_rect_region, // A in diagram
+        {surface_size.first / 2, 0},
+        {0, -1}},
+    RegionWithTestPoints{"right region edge at surface right edge", multi_rect_region, // C in diagram
+        {surface_size.first - 1, surface_size.second / 4},
+        {1, 0}},
+    RegionWithTestPoints{"left region edge inside surface", multi_rect_region, // F in diagram
+        {small_rect_inset, surface_size.second * 3 / 4},
+        {-1, 0}},
+    RegionWithTestPoints{"step edge", multi_rect_region,  // D in diagram
+        {small_rect_inset / 2, surface_size.second / 2 - 1},
+        {0, 1}},
+    RegionWithTestPoints{"bottom clipped edge", multi_rect_region,  // I in diagram
+        {surface_size.first / 2, surface_size.second - 1},
+        {0, 1}});
+
+auto const multi_rect_corners = Values(
+    RegionWithTestPoints{
+        "top-left corner", multi_rect_region, // AxB in diagram
+        {0, 0},
+        {-1, -1}},
+    RegionWithTestPoints{
+        "top-right corner", multi_rect_region, // AxC in diagram
+        {surface_size.first - 1, 0},
+        {1, -1}},
+    RegionWithTestPoints{
+        "bottom-left corner", multi_rect_region, // HxF in diagram
+        {small_rect_inset, surface_size.second - 1},
+        {-1, 1}},
+    RegionWithTestPoints{
+        "bottom-right corner", multi_rect_region, // HxG in diagram
+        {surface_size.first - small_rect_inset - 1, surface_size.second - 1},
+        {1, 1}},
+    RegionWithTestPoints{
+        "left interior corner", multi_rect_region, // HxF in diagram
+        {small_rect_inset, surface_size.second / 2 - 1},
+        {-1, 1}},
+    RegionWithTestPoints{
+        "right interior corner", multi_rect_region, // HxG in diagram
+        {surface_size.first - small_rect_inset - 1, surface_size.second / 2 - 1},
+        {1, 1}});
+
+// TODO: test subtract
+// TODO: test empty region
+// TODO: test default region
+
+TEST_P(RegionSurfaceInputCombinations, input_inside_region_seen)
+{
+    auto const top_left = std::make_pair(64, 49);
+    wlcs::Client client{the_server()};
+    RegionWithTestPoints region;
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+
+    std::tie(region, builder, input) = GetParam();
+
+    auto surface = builder->build(
+        the_server(),
+        client,
+        top_left,
+        region.region.surface_size);
+    region.region.apply_to_surface(client, *surface);
+    struct wl_surface* const wl_surface = *surface;
+
+    auto const device = input->create_device(the_server());
+    device->down_at({
+        top_left.first + region.on_surface.first,
+        top_left.second + region.on_surface.second});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Eq(wl_surface))
+        << input << " not seen by "<< builder << " when inside " << region.name << " of " << region.region.name << " region";
+
+    if (input->current_surface(client) == wl_surface)
+    {
+        EXPECT_THAT(input->position_on_surface(client), Eq(region.on_surface))
+            << input << " in the wrong place over " << builder << " while testing " << region;
+    }
+}
+
+TEST_P(RegionSurfaceInputCombinations, input_not_seen_after_leaving_region)
+{
+    auto const top_left = std::make_pair(64, 49);
+    wlcs::Client client{the_server()};
+    RegionWithTestPoints region;
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(region, builder, input) = GetParam();
+
+    auto surface = builder->build(
+        the_server(),
+        client,
+        top_left,
+        region.region.surface_size);
+    region.region.apply_to_surface(client, *surface);
+    struct wl_surface* const wl_surface = *surface;
+
+    auto const device = input->create_device(the_server());
+    device->down_at({
+        top_left.first + region.on_surface.first,
+        top_left.second + region.on_surface.second});
+    client.roundtrip();
+    device->up();
+    device->down_at({
+        top_left.first + region.off_surface.first,
+        top_left.second + region.off_surface.second});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(wl_surface))
+        << input << " seen by " << builder << " when outside " << region.name << " of " << region.region.name << " region";
+}
+
+class SurfaceInputCombinations :
+    public wlcs::InProcessServer,
+    public testing::WithParamInterface<std::tuple<
+        std::shared_ptr<wlcs::SurfaceBuilder>,
+        std::shared_ptr<wlcs::InputMethod>>>
+{
+};
+
+TEST_P(SurfaceInputCombinations, input_not_seen_in_region_after_null_buffer_committed)
+{
+    auto const top_left = std::make_pair(64, 49);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto const region = full_surface_region;
+
+    auto surface = builder->build(
+        the_server(),
+        client,
+        top_left,
+        region.surface_size);
+    region.apply_to_surface(client, *surface);
+    struct wl_surface* const wl_surface = *surface;
+    wl_surface_attach(wl_surface, nullptr, 0, 0);
+    wl_surface_commit(wl_surface);
+    client.roundtrip();
+
+    auto const device = input->create_device(the_server());
+    device->down_at(top_left);
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(wl_surface))
+        << input << " seen by " << builder << " after null buffer committed";
+}
+
+TEST_P(SurfaceInputCombinations, input_not_seen_in_surface_without_region_after_null_buffer_committed)
+{
+    auto const top_left = std::make_pair(64, 49);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto surface = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    struct wl_surface* const wl_surface = *surface;
+    wl_surface_attach(wl_surface, nullptr, 0, 0);
+    wl_surface_commit(wl_surface);
+    client.roundtrip();
+
+    auto const device = input->create_device(the_server());
+    device->down_at(top_left);
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(wl_surface))
+        << input << " seen by " << builder << " after null buffer committed";
+}
+
+TEST_P(SurfaceInputCombinations, input_not_seen_over_empty_region)
+{
+    auto const top_left = std::make_pair(64, 49);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto surface = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    struct wl_surface* const wl_surface = *surface;
+
+    auto const wl_region = wl_compositor_create_region(client.compositor());
+    wl_surface_set_input_region(*surface, wl_region);
+    wl_region_destroy(wl_region);
+    wl_surface_commit(*surface);
+    client.roundtrip();
+
+    auto const device = input->create_device(the_server());
+    device->down_at({top_left.first + 4, top_left.second + 4});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(wl_surface))
+        << input << " seen by " << builder << " with empty input region";
+}
+
+TEST_P(SurfaceInputCombinations, input_hits_parent_after_falling_through_subsurface)
+{
+    auto const top_left = std::make_pair(64, 49);
+    auto const input_offset = std::make_pair(4, 4);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto parent = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    struct wl_surface* const parent_wl_surface = *parent;
+    auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+        *parent,
+        0, 0,
+        surface_size.first, surface_size.second));
+    wl_subsurface_set_desync(*subsurface);
+    struct wl_surface* const sub_wl_surface = *subsurface;
+
+    Region const region{"region", surface_size, {{RegionAction::add, {0, 0}, {1, 1}}}};
+    region.apply_to_surface(client, sub_wl_surface);
+
+    auto const device = input->create_device(the_server());
+    device->down_at({top_left.first + input_offset.first, top_left.second + input_offset.second});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface))
+        << input << " seen by subsurface when not over region";
+
+    EXPECT_THAT(input->current_surface(client), Eq(parent_wl_surface))
+        << input << " not seen by " << builder << " when it should have fallen through the subsurface input region";
+
+    EXPECT_THAT(input->position_on_surface(client), Eq(input_offset))
+        << input << " seen in the wrong place";
+}
+
+TEST_P(SurfaceInputCombinations, unmapping_parent_stops_subsurface_getting_input)
+{
+    auto const top_left = std::make_pair(64, 49);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto parent = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    struct wl_surface* const parent_wl_surface = *parent;
+    auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+        *parent,
+        0, 0,
+        surface_size.first, surface_size.second));
+    wl_subsurface_set_desync(*subsurface);
+    struct wl_surface* const sub_wl_surface = *subsurface;
+    client.roundtrip();
+
+    wl_surface_attach(parent_wl_surface, nullptr, 0, 0);
+    wl_surface_commit(parent_wl_surface);
+    client.roundtrip();
+
+    auto const device = input->create_device(the_server());
+    device->down_at({top_left.first + 4, top_left.second + 4});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface))
+        << input << " seen by " << builder << " after it was unmapped";
+
+    EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface))
+        << input << " seen by subsurface after parent " << builder << " was unmapped";
+}
+
+TEST_P(SurfaceInputCombinations, input_falls_through_subsurface_when_unmapped)
+{
+    auto const top_left = std::make_pair(200, 49);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto lower = client.create_visible_surface(surface_size.first, surface_size.second);
+    the_server().move_surface_to(lower, top_left.first - 100, top_left.second);
+    struct wl_surface* const lower_wl_surface = lower;
+
+    auto parent = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    ASSERT_THAT(surface_size.first, Gt(100));
+    struct wl_surface* const parent_wl_surface = *parent;
+    auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+        *parent,
+        -100, 0,
+        surface_size.first, surface_size.second));
+    wl_subsurface_set_desync(*subsurface);
+    struct wl_surface* const sub_wl_surface = *subsurface;
+    client.roundtrip();
+
+    wl_surface_attach(sub_wl_surface, nullptr, 0, 0);
+    wl_surface_commit(sub_wl_surface);
+    client.roundtrip();
+
+    auto const device = input->create_device(the_server());
+    device->down_at({top_left.first - 90, top_left.second + 10});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface))
+        << input << " seen by subsurface after it was unmapped";
+
+    EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface))
+        << input << " seen by " << builder << " even through it shouldn't have been over it's input region";
+
+    EXPECT_THAT(input->current_surface(client), Eq(lower_wl_surface))
+        << input << " not seen by lower surface";
+}
+
+TEST_P(SurfaceInputCombinations, input_falls_through_subsurface_when_parent_unmapped)
+{
+    auto const top_left = std::make_pair(200, 49);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto lower = client.create_visible_surface(surface_size.first, surface_size.second);
+    the_server().move_surface_to(lower, top_left.first - 100, top_left.second);
+    struct wl_surface* const lower_wl_surface = lower;
+
+    auto parent = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    ASSERT_THAT(surface_size.first, Gt(100));
+    struct wl_surface* const parent_wl_surface = *parent;
+    auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+        *parent,
+        -100, 0,
+        surface_size.first, surface_size.second));
+    wl_subsurface_set_desync(*subsurface);
+    struct wl_surface* const sub_wl_surface = *subsurface;
+    client.roundtrip();
+
+    wl_surface_attach(parent_wl_surface, nullptr, 0, 0);
+    wl_surface_commit(parent_wl_surface);
+    client.roundtrip();
+
+    auto const device = input->create_device(the_server());
+    device->down_at({top_left.first - 90, top_left.second + 10});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(sub_wl_surface))
+        << input << " seen by subsurface after parent was unmapped";
+
+    EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface))
+        << input << " seen by " << builder << " after being unmapped (also input should have gone to subsurface)";
+
+    EXPECT_THAT(input->current_surface(client), Eq(lower_wl_surface))
+        << input << " not seen by lower surface";
+}
+
+TEST_P(SurfaceInputCombinations, input_seen_after_surface_unmapped_and_remapped)
+{
+    auto const top_left = std::make_pair(200, 49);
+    auto const input_offset = std::make_pair(4, 4);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto surface = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    struct wl_surface* const wl_surface = *surface;
+
+    wl_surface_attach(wl_surface, nullptr, 0, 0);
+    wl_surface_commit(wl_surface);
+    client.roundtrip();
+
+    surface->attach_visible_buffer(surface_size.first, surface_size.second);
+
+    auto const device = input->create_device(the_server());
+    device->down_at({top_left.first + input_offset.first, top_left.second + input_offset.second});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Eq(wl_surface))
+        << input << " not seen by " << builder << " after it was unmapped and remapped";
+
+    EXPECT_THAT(input->position_on_surface(client), Eq(input_offset))
+        << input << " seen in the wrong place";
+}
+
+TEST_P(SurfaceInputCombinations, input_seen_by_subsurface_after_parent_unmapped_and_remapped)
+{
+    auto const top_left = std::make_pair(200, 49);
+    auto const input_offset = std::make_pair(-90, 10);
+    auto const subsurface_offset = std::make_pair(-100, 0);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto parent = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    ASSERT_THAT(surface_size.first, Gt(100));
+    struct wl_surface* const parent_wl_surface = *parent;
+    auto subsurface = std::make_unique<wlcs::Subsurface>(wlcs::Subsurface::create_visible(
+        *parent,
+        subsurface_offset.first, subsurface_offset.second,
+        surface_size.first, surface_size.second));
+    wl_subsurface_set_desync(*subsurface);
+    struct wl_surface* const sub_wl_surface = *subsurface;
+    client.roundtrip();
+
+    wl_surface_attach(parent_wl_surface, nullptr, 0, 0);
+    wl_surface_commit(parent_wl_surface);
+    client.roundtrip();
+
+    parent->attach_visible_buffer(surface_size.first, surface_size.second);
+    //the_server().move_surface_to(*parent, top_left.first, top_left.second);
+
+    auto const device = input->create_device(the_server());
+    device->down_at({top_left.first + input_offset.first, top_left.second + input_offset.second});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(parent_wl_surface))
+        << input << " seen by " << builder << " when it should be seen by it's subsurface";
+
+    EXPECT_THAT(input->current_surface(client), Eq(sub_wl_surface))
+        << input << " not seen by subsurface after parent was unmapped and remapped";
+
+    EXPECT_THAT(input->position_on_surface(client), Eq(
+        std::make_pair(input_offset.first - subsurface_offset.first, input_offset.second - subsurface_offset.second)))
+        << input << " seen in the wrong place";
+}
+
+TEST_P(SurfaceInputCombinations, input_seen_after_dragged_off_surface)
+{
+    auto const top_left = std::make_pair(200, 49);
+    auto const input_offset = std::make_pair(-5, 5);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto other = client.create_visible_surface(100, 100);
+    the_server().move_surface_to(other, top_left.first - 102, top_left.second);
+    struct wl_surface* const other_wl_surface = other;
+
+    auto main = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    struct wl_surface* const main_wl_surface = *main;
+    client.roundtrip();
+
+    auto const device = input->create_device(the_server());
+    device->down_at({top_left.first + 5, top_left.second + 5});
+    client.roundtrip();
+    device->move_to({top_left.first + input_offset.first, top_left.second + input_offset.second});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(other_wl_surface))
+        << input << " seen by second surface event though it was dragged from first";
+
+    EXPECT_THAT(input->current_surface(client), Eq(main_wl_surface))
+        << input << " not seen by " << builder << " after being dragged away";
+
+    EXPECT_THAT(input->position_on_surface(client), Eq(input_offset))
+        << input << " not seen by " << builder << " after being dragged away";
+}
+
+TEST_P(SurfaceInputCombinations, input_seen_by_second_surface_after_drag_off_first_and_up)
+{
+    auto const top_left = std::make_pair(200, 49);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto other = builder->build(
+        the_server(),
+        client,
+        std::make_pair(top_left.first - 102, top_left.second),
+        std::make_pair(100, 100));
+    struct wl_surface* const other_wl_surface = *other;
+
+    auto main = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    struct wl_surface* const main_wl_surface = *main;
+    client.roundtrip();
+
+    auto const device = input->create_device(the_server());
+    device->down_at({top_left.first + 5, top_left.second + 5});
+    client.roundtrip();
+    device->move_to({top_left.first - 80, top_left.second + 5});
+    client.roundtrip();
+    device->up();
+    device->down_at({top_left.first - 80, top_left.second + 5});
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(main_wl_surface))
+        << input << " seen by first " << builder << " after being up";
+
+    EXPECT_THAT(input->current_surface(client), Eq(other_wl_surface))
+        << input << " not seen by second " << builder << " after being up";
+}
+
+// Will only be instantiated with toplevel surfaces
+class ToplevelInputCombinations :
+    public wlcs::InProcessServer,
+    public testing::WithParamInterface<std::tuple<
+        std::shared_ptr<wlcs::SurfaceBuilder>,
+        std::shared_ptr<wlcs::InputMethod>>>
+{
+};
+
+TEST_P(ToplevelInputCombinations, input_falls_through_surface_without_region_after_null_buffer_committed)
+{
+    auto const top_left = std::make_pair(64, 49);
+    wlcs::Client client{the_server()};
+    std::shared_ptr<wlcs::SurfaceBuilder> builder;
+    std::shared_ptr<wlcs::InputMethod> input;
+    std::tie(builder, input) = GetParam();
+
+    auto lower = client.create_visible_surface(surface_size.first, surface_size.second);
+    the_server().move_surface_to(lower, top_left.first, top_left.second);
+    struct wl_surface* const lower_wl_surface = lower;
+
+    auto upper = builder->build(
+        the_server(),
+        client,
+        top_left,
+        surface_size);
+    struct wl_surface* const upper_wl_surface = *upper;
+    wl_surface_attach(upper_wl_surface, nullptr, 0, 0);
+    wl_surface_commit(upper_wl_surface);
+    client.roundtrip();
+
+    auto const device = input->create_device(the_server());
+    device->down_at(top_left);
+    client.roundtrip();
+
+    EXPECT_THAT(input->current_surface(client), Ne(upper_wl_surface))
+        << input << " seen by " << builder << " after null buffer committed";
+
+    EXPECT_THAT(input->current_surface(client), Eq(lower_wl_surface))
+        << input << " not seen by lower toplevel after null buffer committed to " << builder;
+}
+
+// There's way too many region edges/surface type/input device combinations, so we don't run all of them
+// multi_rect_edges covers most cases, so we test it against all surface type/input device combinations
+// We test the rest against just XDG toplevel
+
+INSTANTIATE_TEST_SUITE_P(
+    MultiRectEdges,
+    RegionSurfaceInputCombinations,
+    Combine(multi_rect_edges, all_surface_types, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+    DefaultEdges,
+    RegionSurfaceInputCombinations,
+    Combine(default_edges, all_surface_types, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+    FullSurface,
+    RegionSurfaceInputCombinations,
+    Combine(full_surface_edges, xdg_stable_surface_type, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+    SmallerRegion,
+    RegionSurfaceInputCombinations,
+    Combine(smaller_region_edges, xdg_stable_surface_type, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+    ClippedLargerRegion,
+    RegionSurfaceInputCombinations,
+    Combine(larger_region_edges, xdg_stable_surface_type, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+    MultiRectCorners,
+    RegionSurfaceInputCombinations,
+    Combine(multi_rect_corners, xdg_stable_surface_type, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+    SurfaceInputRegions,
+    SurfaceInputCombinations,
+    Combine(all_surface_types, all_input_types));
+
+INSTANTIATE_TEST_SUITE_P(
+    ToplevelInputRegions,
+    ToplevelInputCombinations,
+    Combine(toplevel_surface_types, all_input_types));
diff --git a/tests/test_bad_buffer.cpp b/tests/test_bad_buffer.cpp
new file mode 100644 (file)
index 0000000..284a694
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <gmock/gmock.h>
+
+#include "helpers.h"
+
+#include "in_process_server.h"
+
+/* tests, that attempt to crash the compositor on purpose */
+
+static struct wl_buffer *
+create_bad_shm_buffer(wlcs::Client& client, int width, int height)
+{
+       struct wl_shm *shm = client.shm();
+       int stride = width * 4;
+       int size = stride * height;
+       struct wl_shm_pool *pool;
+       struct wl_buffer *buffer;
+       int fd;
+
+       fd = wlcs::helpers::create_anonymous_file(size);
+
+       pool = wl_shm_create_pool(shm, fd, size);
+       buffer = wl_shm_pool_create_buffer(
+        pool,
+        0,
+        width,
+        height,
+        stride,
+        WL_SHM_FORMAT_ARGB8888);
+       wl_shm_pool_destroy(pool);
+
+       /* Truncate the file to a small size, so that the compositor
+        * will access it out-of-bounds, and hit SIGBUS.
+        */
+       assert(ftruncate(fd, 12) == 0);
+       close(fd);
+
+       return buffer;
+}
+
+using BadBufferTest = wlcs::InProcessServer;
+
+TEST_F(BadBufferTest, test_truncated_shm_file)
+{
+       using namespace testing;
+
+       wlcs::Client client{the_server()};
+
+       bool buffer_consumed{false};
+
+       auto surface = client.create_visible_surface(200, 200);
+
+       wl_buffer* bad_buffer = create_bad_shm_buffer(client, 200, 200);
+
+       wl_surface_attach(surface, bad_buffer, 0, 0);
+       wl_surface_damage(surface, 0, 0, 200, 200);
+
+       surface.add_frame_callback([&buffer_consumed](int) { buffer_consumed = true; });
+
+       wl_surface_commit(surface);
+
+       try
+       {
+               client.dispatch_until([&buffer_consumed]() { return buffer_consumed; });
+       }
+       catch (wlcs::ProtocolError const& err)
+       {
+               wl_buffer_destroy(bad_buffer);
+               EXPECT_THAT(err.error_code(), Eq(WL_SHM_ERROR_INVALID_FD));
+               EXPECT_THAT(err.interface(), Eq(&wl_buffer_interface));
+               return;
+       }
+
+       FAIL() << "Expected protocol error not raised";
+}
+
+TEST_F(BadBufferTest, client_lies_about_buffer_size)
+{
+    using namespace testing;
+
+    wlcs::Client client{the_server()};
+
+    bool buffer_consumed{false};
+
+    auto surface = client.create_visible_surface(200, 200);
+
+    auto const width = 200, height = 200;
+    auto const incorrect_stride = width;
+
+    auto fd = wlcs::helpers::create_anonymous_file(height * incorrect_stride);
+
+    auto shm_pool = wl_shm_create_pool(client.shm(), fd, height * incorrect_stride);
+
+    auto bad_buffer = wl_shm_pool_create_buffer(
+        shm_pool,
+        0,
+        width, height,
+        incorrect_stride, // Stride is in bytes, not pixels, so this is ¼ the correct value.
+        WL_SHM_FORMAT_ARGB8888);
+
+    wl_shm_pool_destroy(shm_pool);
+    close(fd);
+
+    wl_surface_attach(surface, bad_buffer, 0, 0);
+    wl_surface_damage(surface, 0, 0, 200, 200);
+
+    surface.add_frame_callback([&buffer_consumed](int) { buffer_consumed = true; });
+
+    wl_surface_commit(surface);
+
+    try
+    {
+        client.dispatch_until([&buffer_consumed]() { return buffer_consumed; });
+    }
+    catch (wlcs::ProtocolError const& err)
+    {
+        wl_buffer_destroy(bad_buffer);
+        EXPECT_THAT(err.error_code(), Eq(WL_SHM_ERROR_INVALID_STRIDE));
+        EXPECT_THAT(err.interface(), Eq(&wl_buffer_interface));
+        return;
+    }
+
+    FAIL() << "Expected protocol error not raised";
+}
+
+// This should be identical to the first tests case of the previous test. It tests if a 2nd instance of the server can
+// successfully handle a bad buffer. There have been issues with the server installing a SIGBUS handler (via
+// wl_shm_buffer_begin_access()) that only works for the first server instance.
+
+using SecondBadBufferTest = wlcs::InProcessServer;
+
+TEST_F(SecondBadBufferTest, DISABLED_test_truncated_shm_file)
+{
+       using namespace testing;
+
+       wlcs::Client client{the_server()};
+
+       bool buffer_consumed{false};
+
+       auto surface = client.create_visible_surface(200, 200);
+
+       wl_buffer* bad_buffer = create_bad_shm_buffer(client, 200, 200);
+
+       wl_surface_attach(surface, bad_buffer, 0, 0);
+       wl_surface_damage(surface, 0, 0, 200, 200);
+
+       surface.add_frame_callback([&buffer_consumed](int) { buffer_consumed = true; });
+
+       wl_surface_commit(surface);
+
+       try
+       {
+               client.dispatch_until([&buffer_consumed]() { return buffer_consumed; });
+       }
+       catch (wlcs::ProtocolError const& err)
+       {
+               wl_buffer_destroy(bad_buffer);
+               EXPECT_THAT(err.error_code(), Eq(WL_SHM_ERROR_INVALID_FD));
+               EXPECT_THAT(err.interface(), Eq(&wl_buffer_interface));
+               return;
+       }
+
+       FAIL() << "Expected protocol error not raised";
+}
diff --git a/tests/test_surface_events.cpp b/tests/test_surface_events.cpp
new file mode 100644 (file)
index 0000000..4eb8fc7
--- /dev/null
@@ -0,0 +1,638 @@
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2017 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+
+#include <deque>
+#include <tuple>
+
+#include <gmock/gmock.h>
+
+using ClientSurfaceEventsTest = wlcs::InProcessServer;
+
+//
+//static void
+//check_pointer(struct client *client, int x, int y)
+//{
+//     int sx, sy;
+//
+//     /* check that the client got the global pointer update */
+//     assert(client->test->pointer_x == x);
+//     assert(client->test->pointer_y == y);
+//
+//     /* Does global pointer map onto the surface? */
+//     if (surface_contains(client->surface, x, y)) {
+//             /* check that the surface has the pointer focus */
+//             assert(client->input->pointer->focus == client->surface);
+//
+//             /*
+//              * check that the local surface pointer maps
+//              * to the global pointer.
+//              */
+//             sx = client->input->pointer->x + client->surface->x;
+//             sy = client->input->pointer->y + client->surface->y;
+//             assert(sx == x);
+//             assert(sy == y);
+//     } else {
+//             /*
+//              * The global pointer does not map onto surface.  So
+//              * check that it doesn't have the pointer focus.
+//              */
+//             assert(client->input->pointer->focus == NULL);
+//     }
+//}
+//
+//static void
+//check_pointer_move(struct client *client, int x, int y)
+//{
+//     weston_test_move_pointer(client->test->weston_test, x, y);
+//     client_roundtrip(client);
+//     check_pointer(client, x, y);
+//}
+//
+//TEST(test_pointer_surface_move)
+//{
+//     struct client *client;
+//
+//     client = create_client_and_test_surface(100, 100, 100, 100);
+//     assert(client);
+//
+//     /* move pointer outside of client */
+//     assert(!surface_contains(client->surface, 50, 50));
+//     check_pointer_move(client, 50, 50);
+//
+//     /* move client center to pointer */
+//     move_client(client, 0, 0);
+//     assert(surface_contains(client->surface, 50, 50));
+//     check_pointer(client, 50, 50);
+//}
+//
+//static int
+//output_contains_client(struct client *client)
+//{
+//     struct output *output = client->output;
+//     struct surface *surface = client->surface;
+//
+//     return !(output->x >= surface->x + surface->width
+//             || output->x + output->width <= surface->x
+//             || output->y >= surface->y + surface->height
+//             || output->y + output->height <= surface->y);
+//}
+//
+//static void
+//check_client_move(struct client *client, int x, int y)
+//{
+//     move_client(client, x, y);
+//
+//     if (output_contains_client(client)) {
+//             assert(client->surface->output == client->output);
+//     } else {
+//             assert(client->surface->output == NULL);
+//     }
+//}
+//
+//TEST(test_surface_output)
+//{
+//     struct client *client;
+//     int x, y;
+//
+//     client = create_client_and_test_surface(100, 100, 100, 100);
+//     assert(client);
+//
+//     assert(output_contains_client(client));
+//
+//     /* not visible */
+//     x = 0;
+//     y = -client->surface->height;
+//     check_client_move(client, x, y);
+//
+//     /* visible */
+//     check_client_move(client, x, ++y);
+//
+//     /* not visible */
+//     x = -client->surface->width;
+//     y = 0;
+//     check_client_move(client, x, y);
+//
+//     /* visible */
+//     check_client_move(client, ++x, y);
+//
+//     /* not visible */
+//     x = client->output->width;
+//     y = 0;
+//     check_client_move(client, x, y);
+//
+//     /* visible */
+//     check_client_move(client, --x, y);
+//     assert(output_contains_client(client));
+//
+//     /* not visible */
+//     x = 0;
+//     y = client->output->height;
+//     check_client_move(client, x, y);
+//     assert(!output_contains_client(client));
+//
+//     /* visible */
+//     check_client_move(client, x, --y);
+//     assert(output_contains_client(client));
+//}
+
+struct PointerMotion
+{
+    static int constexpr window_width = 231;
+    static int constexpr window_height = 220;
+    std::string name;
+    int initial_x, initial_y; // Relative to surface top-left
+    int dx, dy;
+};
+
+std::ostream& operator<<(std::ostream& out, PointerMotion const& motion)
+{
+    return out << motion.name;
+}
+
+class SurfacePointerMotionTest :
+    public wlcs::InProcessServer,
+    public testing::WithParamInterface<PointerMotion>
+{
+};
+
+TEST_P(SurfacePointerMotionTest, pointer_movement)
+{
+    using namespace testing;
+
+    auto pointer = the_server().create_pointer();
+
+    wlcs::Client client{the_server()};
+
+    auto const params = GetParam();
+
+    auto surface = client.create_visible_surface(
+        params.window_width,
+        params.window_height);
+
+    int const top_left_x = 23, top_left_y = 231;
+    the_server().move_surface_to(surface, top_left_x, top_left_y);
+
+    auto const wl_surface = static_cast<struct wl_surface*>(surface);
+
+    pointer.move_to(top_left_x + params.initial_x, top_left_y + params.initial_y);
+
+    client.roundtrip();
+
+    EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface));
+
+    /* move pointer; it should now be inside the surface */
+    pointer.move_by(params.dx, params.dy);
+
+    client.roundtrip();
+
+    EXPECT_THAT(client.window_under_cursor(), Eq(wl_surface));
+    EXPECT_THAT(client.pointer_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(params.initial_x + params.dx),
+                    wl_fixed_from_int(params.initial_y + params.dy))));
+
+    /* move pointer back; it should now be outside the surface */
+    pointer.move_by(-params.dx, -params.dy);
+
+    client.roundtrip();
+    EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    PointerCrossingSurfaceCorner,
+    SurfacePointerMotionTest,
+    testing::Values(
+        PointerMotion{"Top-left", -1, -1, 1, 1},
+        PointerMotion{"Bottom-left", -1, PointerMotion::window_height, 1, -1},
+        PointerMotion{"Bottom-right", PointerMotion::window_width, PointerMotion::window_height, -1, -1},
+        PointerMotion{"Top-right", PointerMotion::window_width, -1, -1, 1}
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    PointerCrossingSurfaceEdge,
+    SurfacePointerMotionTest,
+    testing::Values(
+        PointerMotion{
+            "Centre-left",
+            -1, PointerMotion::window_height / 2,
+            1, 0},
+        PointerMotion{
+            "Bottom-centre",
+            PointerMotion::window_width / 2, PointerMotion::window_height,
+            0, -1},
+        PointerMotion{
+            "Centre-right",
+            PointerMotion::window_width, PointerMotion::window_height / 2,
+            -1, 0},
+        PointerMotion{
+            "Top-centre",
+            PointerMotion::window_width / 2, -1,
+            0, 1}
+    ));
+
+TEST_F(ClientSurfaceEventsTest, surface_moves_under_pointer)
+{
+    using namespace testing;
+
+    auto pointer = the_server().create_pointer();
+
+    wlcs::Client client{the_server()};
+
+    auto surface = client.create_visible_surface(100, 100);
+    auto const wl_surface = static_cast<struct wl_surface*>(surface);
+
+    /* Set up the pointer outside the surface */
+    the_server().move_surface_to(surface, 0, 0);
+    pointer.move_to(500, 500);
+
+    client.roundtrip();
+
+    EXPECT_THAT(client.window_under_cursor(), Ne(wl_surface));
+
+    /* move the surface so that it is under the pointer */
+    the_server().move_surface_to(surface, 450, 450);
+
+    client.dispatch_until(
+        [wl_surface, &client]()
+        {
+            return client.window_under_cursor() == wl_surface;
+        });
+
+    EXPECT_THAT(client.window_under_cursor(), Eq(wl_surface));
+    EXPECT_THAT(client.pointer_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(50),
+                    wl_fixed_from_int(50))));
+}
+
+TEST_F(ClientSurfaceEventsTest, surface_moves_over_surface_under_pointer)
+{
+    using namespace testing;
+
+    auto pointer = the_server().create_pointer();
+
+    wlcs::Client client{the_server()};
+
+    auto first_surface = client.create_visible_surface(100, 100);
+    auto second_surface = client.create_visible_surface(100, 100);
+
+    /* Set up the pointer outside the surface */
+    the_server().move_surface_to(first_surface, 0, 0);
+    the_server().move_surface_to(second_surface, 0, 0);
+    pointer.move_to(500, 500);
+
+    client.roundtrip();
+
+    /* move the first surface so that it is under the pointer */
+    the_server().move_surface_to(first_surface, 450, 450);
+
+    bool first_surface_focused{false};
+    client.add_pointer_enter_notification(
+        [&first_surface_focused, &first_surface](wl_surface* surf, auto, auto)
+        {
+            if (surf == first_surface)
+            {
+                first_surface_focused = true;
+            }
+            return false;
+        });
+
+    // Wait until the first surface is focused
+    client.dispatch_until(
+        [&first_surface_focused]()
+        {
+            return first_surface_focused;
+        });
+
+    client.add_pointer_leave_notification(
+        [&first_surface_focused, &first_surface](wl_surface* surf)
+        {
+            if (surf == first_surface)
+            {
+                first_surface_focused = false;
+            }
+            return false;
+        });
+
+    bool second_surface_focused{false};
+    client.add_pointer_enter_notification(
+        [&first_surface_focused, &second_surface_focused, &second_surface](auto surf, auto x, auto y)
+        {
+            if (surf == second_surface)
+            {
+                /*
+                 * Protocol requires that the pointer-leave event is sent before pointer-enter
+                 */
+                EXPECT_FALSE(first_surface_focused);
+                second_surface_focused = true;
+                EXPECT_THAT(x, Eq(wl_fixed_from_int(50)));
+                EXPECT_THAT(y, Eq(wl_fixed_from_int(50)));
+            }
+            return false;
+        });
+
+    the_server().move_surface_to(second_surface, 450, 450);
+
+    client.dispatch_until(
+        [&second_surface_focused]()
+        {
+            return second_surface_focused;
+        });
+}
+
+TEST_F(ClientSurfaceEventsTest, surface_resizes_under_pointer)
+{
+    using namespace testing;
+
+    auto pointer = the_server().create_pointer();
+
+    wlcs::Client client{the_server()};
+
+    auto surface = client.create_visible_surface(100, 100);
+
+    /* Set up the pointer outside the surface */
+    the_server().move_surface_to(surface, 400, 400);
+    pointer.move_to(500, 500);
+
+    client.roundtrip();
+
+    ASSERT_THAT(client.window_under_cursor(), Ne(static_cast<wl_surface*>(surface)));
+
+    bool surface_entered{false};
+    client.add_pointer_enter_notification(
+        [&surface_entered, &surface](wl_surface* entered_surf, wl_fixed_t x, wl_fixed_t y)
+        {
+            EXPECT_THAT(surface, Eq(entered_surf));
+            EXPECT_THAT(x, Eq(wl_fixed_from_int(100)));
+            EXPECT_THAT(y, Eq(wl_fixed_from_int(100)));
+            surface_entered = true;
+            return false;
+        });
+    client.add_pointer_leave_notification(
+        [&surface_entered, &surface](wl_surface* left_surf)
+        {
+            EXPECT_THAT(surface, Eq(left_surf));
+            surface_entered = false;
+            return false;
+        });
+
+    auto larger_buffer = wlcs::ShmBuffer{client, 200, 200};
+    auto smaller_buffer = wlcs::ShmBuffer{client, 50, 50};
+
+    // Resize the surface so that the pointer is now over the top...
+    wl_surface_attach(surface, larger_buffer, 0, 0);
+    wl_surface_commit(surface);
+
+    client.dispatch_until(
+        [&surface_entered]()
+        {
+            return surface_entered;
+        });
+
+    // Resize the surface so that the pointer is no longer over the top...
+    wl_surface_attach(surface, smaller_buffer, 0, 0);
+    wl_surface_commit(surface);
+
+    client.dispatch_until(
+        [&surface_entered]()
+        {
+            return !surface_entered;
+        });
+}
+
+TEST_F(ClientSurfaceEventsTest, surface_moves_while_under_pointer)
+{
+    using namespace testing;
+
+    auto pointer = the_server().create_pointer();
+
+    wlcs::Client client{the_server()};
+
+    auto surface = client.create_visible_surface(100, 100);
+
+    the_server().move_surface_to(surface, 450, 450);
+    pointer.move_to(500, 500);
+
+    std::deque<std::pair<int, int>> surface_movements = {
+        std::make_pair(445, 455),
+        std::make_pair(460, 405),
+        std::make_pair(420, 440),
+        std::make_pair(430, 460),
+        std::make_pair(0, 0)    // The last motion is not checked for
+    };
+
+    client.dispatch_until(
+        [&client, &surface]()
+        {
+            if (client.window_under_cursor() == surface)
+            {
+                EXPECT_THAT(client.pointer_position().first, Eq(wl_fixed_from_int(50)));
+                EXPECT_THAT(client.pointer_position().second, Eq(wl_fixed_from_int(50)));
+                return true;
+            }
+            return false;
+        });
+
+    int expected_x{55}, expected_y{45};
+    client.add_pointer_motion_notification(
+        [this, &surface, &surface_movements, &expected_x, &expected_y]
+            (wl_fixed_t x, wl_fixed_t y)
+        {
+            EXPECT_THAT(x, Eq(wl_fixed_from_int(expected_x)));
+            EXPECT_THAT(y, Eq(wl_fixed_from_int(expected_y)));
+
+            auto next_movement = surface_movements.front();
+            surface_movements.pop_front();
+
+            the_server().move_surface_to(
+                surface,
+                next_movement.first,
+                next_movement.second);
+
+            expected_x = 500 - next_movement.first;
+            expected_y = 500 - next_movement.second;
+            return true;
+        });
+
+    // Do the initial surface move
+    the_server().move_surface_to(
+        surface,
+        surface_movements.front().first,
+        surface_movements.front().second);
+    surface_movements.pop_front();
+
+    client.dispatch_until(
+        [&surface_movements]()
+        {
+            return surface_movements.empty();
+        });
+}
+
+TEST_F(ClientSurfaceEventsTest, frame_timestamp_increases)
+{
+    using namespace testing;
+
+    wlcs::Client client{the_server()};
+
+    auto surface = client.create_visible_surface(100, 100);
+
+    std::array<wlcs::ShmBuffer, 3> buffers = {{
+        wlcs::ShmBuffer{client, 100, 100},
+        wlcs::ShmBuffer{client, 100, 100},
+        wlcs::ShmBuffer{client, 100, 100}
+    }};
+
+    /*
+     * The first buffer must never be released, since it is replaced before
+     * it is committed, therefore it never becomes busy.
+     */
+    wl_surface_attach(surface, buffers[0], 0, 0);
+    wl_surface_attach(surface, buffers[1], 0, 0);
+
+    int prev_frame_time = 0;
+    int frame_callback_count = 0;
+    surface.add_frame_callback(
+        [&](int time)
+        {
+            EXPECT_THAT(time, Gt(prev_frame_time));
+            prev_frame_time = time;
+            frame_callback_count++;
+        });
+    wl_surface_commit(surface);
+
+    /**
+     * We need to sleep for multiple miliseconds to make sure the timestamp
+     * really does go up
+     */
+    usleep(10000);
+
+    wl_surface_attach(surface, buffers[2], 0, 0);
+    wl_surface_commit(surface);
+
+    client.dispatch_until([&frame_callback_count]()
+        {
+            return frame_callback_count >= 2;
+        });
+}
+
+TEST_F(ClientSurfaceEventsTest, buffer_release)
+{
+    wlcs::Client client{the_server()};
+
+    auto surface = client.create_visible_surface(100, 100);
+
+    std::array<wlcs::ShmBuffer, 3> buffers = {{
+        wlcs::ShmBuffer{client, 100, 100},
+        wlcs::ShmBuffer{client, 100, 100},
+        wlcs::ShmBuffer{client, 100, 100}
+    }};
+    std::array<bool, 3> buffer_released = {{
+        false,
+        false,
+        false
+    }};
+
+    for (auto i = 0u; i < buffers.size(); ++i)
+    {
+        buffers[i].add_release_listener(
+            [released = &buffer_released[i]]()
+            {
+                *released = true;
+                return false;
+            });
+    }
+
+    /*
+     * The first buffer must never be released, since it is replaced before
+     * it is committed, therefore it never becomes busy.
+     */
+    wl_surface_attach(surface, buffers[0], 0, 0);
+    wl_surface_attach(surface, buffers[1], 0, 0);
+
+    bool frame_consumed{false};
+    surface.add_frame_callback(
+        [&frame_consumed](auto)
+        {
+            frame_consumed = true;
+        });
+    wl_surface_commit(surface);
+
+    client.dispatch_until(
+        [&frame_consumed]()
+        {
+            return frame_consumed;
+        });
+
+    EXPECT_FALSE(buffer_released[0]);
+    // buffers[1] may or may not be released
+    EXPECT_FALSE(buffer_released[2]);
+
+    wl_surface_attach(surface, buffers[2], 0, 0);
+
+    frame_consumed = false;
+    surface.add_frame_callback(
+        [&frame_consumed](auto)
+        {
+            frame_consumed = true;
+        });
+    wl_surface_commit(surface);
+
+    client.dispatch_until(
+        [&frame_consumed]()
+        {
+            return frame_consumed;
+        });
+    EXPECT_FALSE(buffer_released[0]);
+    EXPECT_TRUE(buffer_released[1]);
+    // buffer[2] may or may not be released
+
+    wlcs::ShmBuffer final_buffer(client, 100, 100);
+    wl_surface_attach(surface, final_buffer, 0, 0);
+
+    frame_consumed = false;
+    surface.add_frame_callback(
+        [&frame_consumed](auto)
+        {
+            frame_consumed = true;
+        });
+    wl_surface_commit(surface);
+
+    client.dispatch_until(
+        [&frame_consumed]()
+        {
+            return frame_consumed;
+        });
+
+    EXPECT_FALSE(buffer_released[0]);
+    EXPECT_TRUE(buffer_released[1]);
+    EXPECT_TRUE(buffer_released[2]);
+}
+
+// TODO: make parameterized for different types of shell surfaces
diff --git a/tests/touches.cpp b/tests/touches.cpp
new file mode 100644 (file)
index 0000000..ab4c499
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+#include "surface_builder.h"
+
+#include <gmock/gmock.h>
+
+#include <vector>
+#include <memory>
+
+using namespace testing;
+
+class TouchTest:
+    public wlcs::InProcessServer,
+    public testing::WithParamInterface<std::shared_ptr<wlcs::SurfaceBuilder>>
+{
+};
+
+TEST_P(TouchTest, touch_on_surface_seen)
+{
+    int const window_width = 300, window_height = 300;
+    int const window_top_left_x = 64, window_top_left_y = 7;
+
+    wlcs::Client client{the_server()};
+    auto const surface = GetParam()->build(
+        the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height});
+    auto const wl_surface = static_cast<struct wl_surface*>(*surface);
+
+    auto touch = the_server().create_touch();
+    int touch_x = window_top_left_x + 27;
+    int touch_y = window_top_left_y + 8;
+
+    touch.down_at(touch_x, touch_y);
+    client.roundtrip();
+    ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface";
+    ASSERT_THAT(client.touch_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(touch_x - window_top_left_x),
+                    wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch came down in the wrong place";
+    touch.up();
+    client.roundtrip();
+}
+
+TEST_P(TouchTest, touch_and_drag_on_surface_seen)
+{
+    int const window_width = 300, window_height = 300;
+    int const window_top_left_x = 64, window_top_left_y = 12;
+    int const touch_x = window_top_left_x + 27, touch_y = window_top_left_y + 140;
+    int const dx = 37, dy = -52;
+
+    wlcs::Client client{the_server()};
+    auto const surface = GetParam()->build(
+        the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height});
+    auto const wl_surface = static_cast<struct wl_surface*>(*surface);
+
+    auto touch = the_server().create_touch();
+
+    touch.down_at(touch_x, touch_y);
+    client.roundtrip();
+    ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface";
+    ASSERT_THAT(client.touch_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(touch_x - window_top_left_x),
+                    wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch came down in the wrong place";
+
+    touch.move_to(touch_x + dx, touch_y + dy);
+    client.roundtrip();
+    ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "surface was unfocused when it shouldn't have been";
+    ASSERT_THAT(client.touch_position(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(touch_x - window_top_left_x),
+                    wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch did not move";
+    ASSERT_THAT(client.touch_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(touch_x - window_top_left_x + dx),
+                    wl_fixed_from_int(touch_y - window_top_left_y + dy)))) << "touch did not end up in the right place";
+
+    touch.up();
+    client.roundtrip();
+}
+
+TEST_P(TouchTest, touch_drag_outside_of_surface_and_back_not_lost)
+{
+    int const window_width = 300, window_height = 300;
+    int const window_top_left_x = 64, window_top_left_y = 12;
+    int const touch_a_x = window_top_left_x + 27, touch_a_y = window_top_left_y + 12;
+    int const touch_b_x = window_top_left_x - 6, touch_b_y = window_top_left_y + window_width + 8;
+
+    wlcs::Client client{the_server()};
+    auto const surface = GetParam()->build(
+        the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height});
+    auto const wl_surface = static_cast<struct wl_surface*>(*surface);
+
+    auto touch = the_server().create_touch();
+
+    touch.down_at(touch_a_x, touch_a_y);
+    client.roundtrip();
+    ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface";
+    ASSERT_THAT(client.touch_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(touch_a_x - window_top_left_x),
+                    wl_fixed_from_int(touch_a_y - window_top_left_y)))) << "touch came down in the wrong place";
+
+    touch.move_to(touch_b_x, touch_b_y);
+    client.roundtrip();
+    EXPECT_THAT(client.touched_window(), Eq(wl_surface)) << "touch was lost when it moved out of the surface";
+    if (client.touched_window() == wl_surface)
+    {
+        EXPECT_THAT(client.touch_position(),
+                    Eq(std::make_pair(
+                        wl_fixed_from_int(touch_b_x - window_top_left_x),
+                        wl_fixed_from_int(touch_b_y - window_top_left_y)))) << "touch did not end up in the right place outside of the surface";
+    }
+
+    touch.move_to(touch_a_x, touch_a_y);
+    client.roundtrip();
+    EXPECT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not come back onto surface";
+    EXPECT_THAT(client.touch_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(touch_a_x - window_top_left_x),
+                    wl_fixed_from_int(touch_a_y - window_top_left_y)))) << "touch came back in the wrong place";
+
+    touch.up();
+    client.roundtrip();
+}
+
+TEST_P(TouchTest, sends_touch_up_on_surface_destroy)
+{
+    int const window_width = 300, window_height = 300;
+    int const window_top_left_x = 64, window_top_left_y = 7;
+
+    wlcs::Client client{the_server()};
+    auto surface = GetParam()->build(
+        the_server(), client, {window_top_left_x, window_top_left_y}, {window_width, window_height});
+
+    auto touch = the_server().create_touch();
+    int touch_x = window_top_left_x + 27;
+    int touch_y = window_top_left_y + 8;
+    touch.down_at(touch_x, touch_y);
+    client.roundtrip();
+
+    surface.reset();
+    client.roundtrip();
+
+    ASSERT_THAT(client.touched_window(), Eq(nullptr)) << "Touch did not leave surface when surface was destroyed";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllSurfaceTypes,
+    TouchTest,
+    ValuesIn(wlcs::SurfaceBuilder::all_surface_types()));
diff --git a/tests/wl_output.cpp b/tests/wl_output.cpp
new file mode 100644 (file)
index 0000000..9b0fb3b
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+
+#include <memory>
+
+using namespace testing;
+using namespace wlcs;
+
+using WlOutputTest = wlcs::InProcessServer;
+
+TEST_F(WlOutputTest, wl_output_properties_set)
+{
+    wlcs::Client client{the_server()};
+
+    ASSERT_THAT(client.output_count(), Ge(1u));
+    auto output = client.output_state(0);
+
+    EXPECT_THAT((bool)output.geometry_position, Eq(true));
+    EXPECT_THAT((bool)output.mode_size, Eq(true));
+    EXPECT_THAT((bool)output.scale, Eq(true));
+}
+
+TEST_F(WlOutputTest, wl_output_release)
+{
+    wlcs::Client client{the_server()};
+
+    {
+        // Acquire *any* wl_output; we don't care which
+        auto const output =
+            client.bind_if_supported<wl_output>(wlcs::AtLeastVersion{WL_OUTPUT_RELEASE_SINCE_VERSION});
+        client.roundtrip();
+    }
+    // output is now released
+
+    client.roundtrip();
+}
diff --git a/tests/wlr_foreign_toplevel_management_v1.cpp b/tests/wlr_foreign_toplevel_management_v1.cpp
new file mode 100644 (file)
index 0000000..25ab5f9
--- /dev/null
@@ -0,0 +1,918 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "version_specifier.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+#include "generated/wlr-foreign-toplevel-management-unstable-v1-client.h"
+
+#include <gmock/gmock.h>
+#include <boost/throw_exception.hpp>
+#include <algorithm>
+
+using namespace testing;
+
+namespace wlcs
+{
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_foreign_toplevel_manager_v1)
+WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_foreign_toplevel_handle_v1)
+}
+
+namespace
+{
+int const w = 10, h = 15;
+
+class ForeignToplevelHandle
+{
+public:
+    ForeignToplevelHandle(zwlr_foreign_toplevel_handle_v1* handle);
+    ForeignToplevelHandle(ForeignToplevelHandle const&) = delete;
+    ForeignToplevelHandle& operator=(ForeignToplevelHandle const&) = delete;
+
+    auto is_dirty() const { return dirty_; }
+    auto title() const { return title_; }
+    auto app_id() const { return app_id_; }
+    auto outputs() const -> std::vector<wl_output*> const& { return outputs_; }
+    auto maximized() const { return maximized_; }
+    auto minimized() const { return minimized_; }
+    auto activated() const { return activated_; }
+    auto fullscreen() const { return fullscreen_; }
+    auto destroyed() const { return destroyed_; }
+
+    operator zwlr_foreign_toplevel_handle_v1*() const { return handle; }
+    wlcs::WlHandle<zwlr_foreign_toplevel_handle_v1> const handle;
+
+private:
+    static auto get_self(void* data) -> ForeignToplevelHandle*
+    {
+        return static_cast<ForeignToplevelHandle*>(data);
+    }
+
+    bool dirty_{false};
+    std::experimental::optional<std::string> title_;
+    std::experimental::optional<std::string> app_id_;
+    std::vector<wl_output*> outputs_;
+    bool maximized_{false}, minimized_{false}, activated_{false}, fullscreen_{false};
+
+    bool destroyed_{false};
+};
+
+ForeignToplevelHandle::ForeignToplevelHandle(zwlr_foreign_toplevel_handle_v1* handle)
+    : handle{handle}
+{
+    static zwlr_foreign_toplevel_handle_v1_listener const listener = {
+        [] /* title */ (
+            void* data,
+            zwlr_foreign_toplevel_handle_v1*,
+            char const* title)
+            {
+                auto self = get_self(data);
+                self->title_ = title;
+                self->dirty_ = true;
+            },
+        [] /* app_id */ (
+            void* data,
+            zwlr_foreign_toplevel_handle_v1*,
+            char const* app_id)
+            {
+                auto self = get_self(data);
+                self->app_id_ = app_id;
+                self->dirty_ = true;
+            },
+        [] /* output_enter */ (
+            void* data,
+            zwlr_foreign_toplevel_handle_v1*,
+            wl_output* output)
+            {
+                auto self = get_self(data);
+                self->outputs_.push_back(output);
+                self->dirty_ = true;
+            },
+        [] /* output_leave */ (
+            void* data,
+            zwlr_foreign_toplevel_handle_v1*,
+            wl_output* output)
+            {
+                auto self = get_self(data);
+                std::remove(
+                    self->outputs_.begin(),
+                    self->outputs_.end(),
+                    output);
+                self->dirty_ = true;
+            },
+        [] /*state */ (
+            void* data,
+            zwlr_foreign_toplevel_handle_v1*,
+            wl_array* state)
+            {
+                auto self = get_self(data);
+
+                self->maximized_ = false;
+                self->minimized_ = false;
+                self->activated_ = false;
+                self->fullscreen_ = false;
+
+                for (auto item = static_cast<zwlr_foreign_toplevel_handle_v1_state*>(state->data);
+                    (char*)item < static_cast<char*>(state->data) + state->size;
+                    item++)
+                {
+                    switch (*item)
+                    {
+                    case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED:
+                        self->maximized_ = true;
+                        break;
+
+                    case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED:
+                        self->minimized_ = true;
+                        break;
+
+                    case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED:
+                        self->activated_ = true;
+                        break;
+
+                    case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN:
+                        self->fullscreen_ = true;
+                        break;
+                    }
+                }
+                self->dirty_ = true;
+            },
+        [] /* done */ (
+            void* data,
+            zwlr_foreign_toplevel_handle_v1*)
+            {
+                auto self = get_self(data);
+                self->dirty_ = false;
+            },
+        [] /* closed */ (
+            void* data,
+            zwlr_foreign_toplevel_handle_v1*)
+            {
+                auto self = get_self(data);
+                self->destroyed_ = true;
+                self->dirty_ = false;
+            }
+    };
+    zwlr_foreign_toplevel_handle_v1_add_listener(handle, &listener, this);
+}
+
+class ForeignToplevelManager
+{
+public:
+    ForeignToplevelManager(wlcs::Client& client);
+
+    auto toplevels() -> std::vector<std::unique_ptr<ForeignToplevelHandle>> const&
+    {
+        toplevels_.erase(
+            std::remove_if(
+                toplevels_.begin(),
+                toplevels_.end(),
+                [](auto const& toplevel) { return toplevel->destroyed(); }),
+            toplevels_.end());
+
+        return toplevels_;
+    }
+
+    wlcs::WlHandle<zwlr_foreign_toplevel_manager_v1> const manager;
+
+private:
+    std::vector<std::unique_ptr<ForeignToplevelHandle>> toplevels_;
+};
+
+ForeignToplevelManager::ForeignToplevelManager(wlcs::Client& client)
+    : manager{client.bind_if_supported<zwlr_foreign_toplevel_manager_v1>(wlcs::AnyVersion)}
+{
+    static zwlr_foreign_toplevel_manager_v1_listener const listener = {
+        +[] /* toplevel */ (
+            void* data,
+            zwlr_foreign_toplevel_manager_v1*,
+            zwlr_foreign_toplevel_handle_v1* toplevel)
+            {
+                auto self = static_cast<ForeignToplevelManager*>(data);
+                auto handle = std::make_unique<ForeignToplevelHandle>(toplevel);
+                self->toplevels_.push_back(std::move(handle));
+            },
+        +[] /* finished */ (
+            void* /*data*/,
+            zwlr_foreign_toplevel_manager_v1 *)
+            {
+            }
+    };
+    zwlr_foreign_toplevel_manager_v1_add_listener(manager, &listener, this);
+}
+
+class ForeignToplevelManagerTest
+    : public wlcs::StartedInProcessServer
+{
+};
+
+class ForeignToplevelHandleTest
+    : public wlcs::StartedInProcessServer
+{
+public:
+    ForeignToplevelHandleTest()
+        : client{the_server()},
+          manager{client},
+          surface{client},
+          xdg_surface{client, surface},
+          xdg_toplevel{xdg_surface}
+    {
+    }
+
+    auto toplevel() -> ForeignToplevelHandle const&
+    {
+        if (manager.toplevels().empty())
+            BOOST_THROW_EXCEPTION(std::runtime_error("Manager does not know about any toplevels"));
+        if (manager.toplevels().size() > 1u)
+            BOOST_THROW_EXCEPTION(std::runtime_error(
+                "Manager knows about " + std::to_string(manager.toplevels().size()) + " toplevels"));
+        if (manager.toplevels()[0]->is_dirty())
+            BOOST_THROW_EXCEPTION(std::runtime_error("Toplevel has pending updates"));
+        return *manager.toplevels()[0];
+    }
+
+    auto toplevel(std::string const& app_id) -> ForeignToplevelHandle const&
+    {
+        std::experimental::optional<ForeignToplevelHandle const*> match;
+        for (auto const& i : manager.toplevels())
+        {
+            if (i->app_id() == app_id)
+            {
+                if (match)
+                    BOOST_THROW_EXCEPTION(std::runtime_error("Multiple toplevels have the same app ID " + app_id));
+                else
+                    match = i.get();
+            }
+        }
+        if (!match)
+            BOOST_THROW_EXCEPTION(std::runtime_error("No toplevels have the app ID " + app_id));
+        if (match.value()->is_dirty())
+            BOOST_THROW_EXCEPTION(std::runtime_error("Toplevel has pending updates"));
+        return *match.value();
+    }
+
+    wlcs::Client client;
+    ForeignToplevelManager manager;
+    wlcs::Surface surface;
+    wlcs::XdgSurfaceStable xdg_surface;
+    wlcs::XdgToplevelStable xdg_toplevel;
+};
+}
+
+TEST_F(ForeignToplevelManagerTest, does_not_detect_toplevels_when_test_creates_none)
+{
+    wlcs::Client client{the_server()};
+    ForeignToplevelManager manager{client};
+    client.roundtrip();
+    ASSERT_THAT(manager.toplevels().size(), Eq(0u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_toplevel_from_same_client)
+{
+    wlcs::Client client{the_server()};
+
+    auto surface{client.create_visible_surface(w, h)};
+
+    ForeignToplevelManager manager{client};
+    client.roundtrip();
+    ASSERT_THAT(manager.toplevels().size(), Eq(1u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_toplevel_from_different_client)
+{
+    wlcs::Client foreign_client{the_server()};
+    wlcs::Client observer_client{the_server()};
+
+    auto surface{foreign_client.create_visible_surface(w, h)};
+
+    ForeignToplevelManager manager{observer_client};
+    observer_client.roundtrip();
+    ASSERT_THAT(manager.toplevels().size(), Eq(1u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_toplevel_created_after_manager)
+{
+    wlcs::Client client{the_server()};
+
+    ForeignToplevelManager manager{client};
+    client.roundtrip();
+    ASSERT_THAT(manager.toplevels().size(), Eq(0u));
+
+    auto surface{client.create_visible_surface(w, h)};
+
+    client.roundtrip();
+    ASSERT_THAT(manager.toplevels().size(), Eq(1u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_multiple_toplevels_from_multiple_clients)
+{
+    wlcs::Client foreign_client{the_server()};
+    wlcs::Client observer_client{the_server()};
+
+    auto foreign_surface{foreign_client.create_visible_surface(w, h)};
+    auto observer_surface{observer_client.create_visible_surface(w, h)};
+
+    ForeignToplevelManager manager{observer_client};
+    observer_client.roundtrip();
+    ASSERT_THAT(manager.toplevels().size(), Eq(2u));
+}
+
+TEST_F(ForeignToplevelManagerTest, detects_toplevel_closed)
+{
+    wlcs::Client client{the_server()};
+
+    ForeignToplevelManager manager{client};
+    client.roundtrip();
+    EXPECT_THAT(manager.toplevels().size(), Eq(0u));
+
+    {
+        wlcs::Surface other{client};
+        wlcs::XdgSurfaceStable other_xdg{client, other};
+        wlcs::XdgToplevelStable other_toplevel{other_xdg};
+        other.attach_visible_buffer(w, h);
+        client.roundtrip();
+
+        EXPECT_THAT(manager.toplevels().size(), Eq(1u));
+    }
+
+    client.roundtrip();
+    ASSERT_THAT(manager.toplevels().size(), Eq(0u));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_title)
+{
+    std::string title = "Test Title @!\\-";
+
+    xdg_toplevel_set_title(xdg_toplevel, title.c_str());
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT((bool)toplevel().title(), Eq(true));
+    EXPECT_THAT(toplevel().title().value(), Eq(title));
+}
+
+TEST_F(ForeignToplevelHandleTest, title_gets_updated)
+{
+    std::string title_a = "Test Title @!\\-";
+    std::string title_b = "Title 2";
+
+    xdg_toplevel_set_title(xdg_toplevel, title_a.c_str());
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT((bool)toplevel().title(), Eq(true));
+    ASSERT_THAT(toplevel().title().value(), Eq(title_a));
+
+    xdg_toplevel_set_title(xdg_toplevel, title_b.c_str());
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT((bool)toplevel().title(), Eq(true));
+    EXPECT_THAT(toplevel().title().value(), Eq(title_b));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_app_id)
+{
+    std::string app_id = "fake.wlcs.app.id";
+
+    xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT((bool)toplevel().app_id(), Eq(true));
+    EXPECT_THAT(toplevel().app_id().value(), Eq(app_id));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_maximized)
+{
+    xdg_toplevel_set_maximized(xdg_toplevel);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().maximized(), Eq(true));
+
+    xdg_toplevel_unset_maximized(xdg_toplevel);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().maximized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_minimized)
+{
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().minimized(), Eq(false));
+
+    xdg_toplevel_set_minimized(xdg_toplevel);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().minimized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_fullscreen)
+{
+    xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().fullscreen(), Eq(true));
+
+    xdg_toplevel_unset_fullscreen(xdg_toplevel);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().maximized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_activated)
+{
+    std::string app_id = "fake.wlcs.app.id";
+    xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().activated(), Eq(true));
+
+    std::string other_app_id = "other.app.id";
+    wlcs::Surface other{client};
+    wlcs::XdgSurfaceStable other_xdg{client, other};
+    wlcs::XdgToplevelStable other_toplevel{other_xdg};
+    xdg_toplevel_set_app_id(other_toplevel, other_app_id.c_str());
+    other.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel(app_id).activated(), Eq(false));
+    EXPECT_THAT(toplevel(other_app_id).activated(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_maximize_foreign)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    xdg_toplevel_unset_maximized(xdg_toplevel);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(toplevel().maximized(), Eq(false));
+
+    zwlr_foreign_toplevel_handle_v1_set_maximized(toplevel());
+    client.roundtrip();
+
+    EXPECT_THAT(state.maximized, Eq(true));
+    EXPECT_THAT(toplevel().maximized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unmaximize_foreign)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    xdg_toplevel_set_maximized(xdg_toplevel);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(state.maximized, Eq(true));
+    EXPECT_THAT(toplevel().maximized(), Eq(true));
+
+    zwlr_foreign_toplevel_handle_v1_unset_maximized(toplevel());
+    client.roundtrip();
+
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(toplevel().maximized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_fullscreen_foreign)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(toplevel().fullscreen(), Eq(false));
+
+    zwlr_foreign_toplevel_handle_v1_set_fullscreen(toplevel(), nullptr);
+    client.roundtrip();
+
+    EXPECT_THAT(state.fullscreen, Eq(true));
+    EXPECT_THAT(toplevel().fullscreen(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unfullscreen_foreign)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(state.fullscreen, Eq(true));
+    EXPECT_THAT(toplevel().fullscreen(), Eq(true));
+
+    zwlr_foreign_toplevel_handle_v1_unset_fullscreen(toplevel());
+    client.roundtrip();
+
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(toplevel().fullscreen(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_minimize_foreign)
+{
+    std::string app_id = "fake.wlcs.app.id";
+
+    wlcs::Surface below_surface{client.create_visible_surface(w, h)};
+    the_server().move_surface_to(below_surface, 0, 0);
+    client.roundtrip();
+
+    xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+    surface.attach_visible_buffer(w, h);
+    the_server().move_surface_to(surface, 0, 0);
+    client.roundtrip();
+
+    auto pointer = the_server().create_pointer();
+    pointer.move_to(1, 1);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel(app_id).minimized(), Eq(false)) << "precondition failed";
+    ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)surface)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_set_minimized(toplevel(app_id));
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel(app_id).minimized(), Eq(true));
+
+    pointer.move_to(2, 2);
+    client.roundtrip();
+
+    EXPECT_THAT(client.window_under_cursor(), Ne((wl_surface*)surface))
+        << "surface under pointer when it should have been minimized";
+    EXPECT_THAT(client.window_under_cursor(), Eq((wl_surface*)below_surface))
+        << "surface under pointer not correct";
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unminimize_foreign)
+{
+    std::string app_id = "fake.wlcs.app.id";
+
+    wlcs::Surface below_surface{client.create_visible_surface(w, h)};
+    the_server().move_surface_to(below_surface, 0, 0);
+
+    xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+    surface.attach_visible_buffer(w, h);
+    the_server().move_surface_to(surface, 0, 0);
+    client.roundtrip();
+
+    xdg_toplevel_set_minimized(xdg_toplevel);
+    client.roundtrip();
+
+    auto pointer = the_server().create_pointer();
+    pointer.move_to(1, 1);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel(app_id).minimized(), Eq(true)) << "precondition failed";
+    ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)below_surface)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel(app_id));
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel(app_id).minimized(), Eq(false));
+
+    pointer.move_to(2, 2);
+    client.roundtrip();
+
+    EXPECT_THAT(client.window_under_cursor(), Ne((wl_surface*)below_surface))
+        << "surface under pointer when it should have been occluded by unminimized surface";
+    EXPECT_THAT(client.window_under_cursor(), Eq((wl_surface*)surface))
+        << "surface under pointer not correct";
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unminimize_foreign_to_restored)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    surface.attach_visible_buffer(w, h);
+    xdg_toplevel_unset_maximized(xdg_toplevel);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().maximized(), Eq(false)) << "precondition failed";
+
+    xdg_toplevel_set_minimized(xdg_toplevel);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel());
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().minimized(), Eq(false));
+    EXPECT_THAT(toplevel().maximized(), Eq(false));
+    EXPECT_THAT(state.maximized, Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unminimize_foreign_to_maximized)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    surface.attach_visible_buffer(w, h);
+    xdg_toplevel_set_maximized(xdg_toplevel);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().maximized(), Eq(true)) << "precondition failed";
+
+    xdg_toplevel_set_minimized(xdg_toplevel);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel());
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().minimized(), Eq(false));
+    EXPECT_THAT(toplevel().maximized(), Eq(true));
+    EXPECT_THAT(state.maximized, Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_activated_when_unminimized)
+{
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(false)) << "precondition failed";
+
+    xdg_toplevel_set_minimized(xdg_toplevel);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel());
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().activated(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_unminimized_when_activated)
+{
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(false)) << "precondition failed";
+
+    xdg_toplevel_set_minimized(xdg_toplevel);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_activate(toplevel(), client.seat());
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().minimized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_unminimized_when_maximized)
+{
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(false)) << "precondition failed";
+
+    xdg_toplevel_set_minimized(xdg_toplevel);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_set_maximized(toplevel());
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().minimized(), Eq(false));
+    EXPECT_THAT(toplevel().maximized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, gets_unminimized_when_fullscreened)
+{
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(false)) << "precondition failed";
+
+    xdg_toplevel_set_minimized(xdg_toplevel);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(toplevel().minimized(), Eq(true)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_set_fullscreen(toplevel(), nullptr);
+    client.roundtrip();
+
+    EXPECT_THAT(toplevel().minimized(), Eq(false));
+    EXPECT_THAT(toplevel().fullscreen(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unfullscreen_foreign_to_restored)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    xdg_toplevel_unset_maximized(xdg_toplevel);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT(state.maximized, Eq(false)) << "precondition failed";
+    ASSERT_THAT(toplevel().maximized(), Eq(false)) << "precondition failed";
+
+    xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(state.fullscreen, Eq(true)) << "precondition failed";
+    ASSERT_THAT(toplevel().fullscreen(), Eq(true)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_unset_fullscreen(toplevel());
+    client.roundtrip();
+
+    ASSERT_THAT(state.fullscreen, Eq(false)) << "precondition failed";
+    ASSERT_THAT(toplevel().fullscreen(), Eq(false)) << "precondition failed";
+
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(toplevel().maximized(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_unfullscreen_foreign_to_maximized)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    xdg_toplevel_set_maximized(xdg_toplevel);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT(state.maximized, Eq(true)) << "precondition failed";
+    ASSERT_THAT(toplevel().maximized(), Eq(true)) << "precondition failed";
+
+    xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(state.fullscreen, Eq(true)) << "precondition failed";
+    ASSERT_THAT(toplevel().fullscreen(), Eq(true)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_unset_fullscreen(toplevel());
+    client.roundtrip();
+
+    ASSERT_THAT(state.fullscreen, Eq(false)) << "precondition failed";
+    ASSERT_THAT(toplevel().fullscreen(), Eq(false)) << "precondition failed";
+
+    EXPECT_THAT(state.maximized, Eq(true));
+    EXPECT_THAT(toplevel().maximized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_maximize_foreign_while_fullscreen)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    xdg_toplevel_unset_maximized(xdg_toplevel);
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    ASSERT_THAT(state.maximized, Eq(false)) << "precondition failed";
+    ASSERT_THAT(toplevel().maximized(), Eq(false)) << "precondition failed";
+
+    xdg_toplevel_set_fullscreen(xdg_toplevel, nullptr);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(state.fullscreen, Eq(true)) << "precondition failed";
+    ASSERT_THAT(toplevel().fullscreen(), Eq(true)) << "precondition failed";
+
+    zwlr_foreign_toplevel_handle_v1_set_maximized(toplevel());
+    client.roundtrip();
+
+    EXPECT_THAT(state.fullscreen, Eq(true)) << "XDG toplevel became not fullscreen after requesting maximized";
+    EXPECT_THAT(toplevel().fullscreen(), Eq(true)) << "foreign toplevel became not fullscreen after maximize";
+
+    zwlr_foreign_toplevel_handle_v1_unset_fullscreen(toplevel());
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    ASSERT_THAT(state.fullscreen, Eq(false)) << "precondition failed";
+    ASSERT_THAT(toplevel().fullscreen(), Eq(false)) << "precondition failed";
+
+    EXPECT_THAT(state.maximized, Eq(true));
+    EXPECT_THAT(toplevel().maximized(), Eq(true));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_activate_foreign)
+{
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+    xdg_toplevel.add_configure_notification(
+        [&state](int32_t width, int32_t height, struct wl_array *states)
+        {
+            state = wlcs::XdgToplevelStable::State{width, height, states};
+        });
+    std::string app_id = "fake.wlcs.app.id";
+    xdg_toplevel_set_app_id(xdg_toplevel, app_id.c_str());
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    std::string other_app_id = "other.app.id";
+    wlcs::Surface other{client};
+    wlcs::XdgSurfaceStable other_xdg{client, other};
+    wlcs::XdgToplevelStable other_toplevel{other_xdg};
+    xdg_toplevel_set_app_id(other_toplevel, other_app_id.c_str());
+    other.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(state.activated, Eq(false));
+    EXPECT_THAT(toplevel(app_id).activated(), Eq(false));
+    EXPECT_THAT(toplevel(other_app_id).activated(), Eq(true));
+
+    zwlr_foreign_toplevel_handle_v1_activate(toplevel(app_id), client.seat());
+    client.roundtrip();
+
+    EXPECT_THAT(state.activated, Eq(true));
+    EXPECT_THAT(toplevel(app_id).activated(), Eq(true));
+    EXPECT_THAT(toplevel(other_app_id).activated(), Eq(false));
+}
+
+TEST_F(ForeignToplevelHandleTest, can_close_foreign)
+{
+    bool should_close{false};
+    xdg_toplevel.add_close_notification(
+        [&should_close]()
+        {
+            should_close = true;
+        });
+    surface.attach_visible_buffer(w, h);
+    client.roundtrip();
+
+    EXPECT_THAT(should_close, Eq(false));
+
+    zwlr_foreign_toplevel_handle_v1_close(toplevel());
+    client.roundtrip();
+
+    EXPECT_THAT(should_close, Eq(true));
+}
+
+// TODO: test toplevel outputs
+// TODO: test fullscreening toplevel on a specific output
+// TODO: test popup does not come up as toplevel
+// TODO: test zwlr_foreign_toplevel_handle_v1_set_rectangle somehow
diff --git a/tests/wlr_layer_shell_v1.cpp b/tests/wlr_layer_shell_v1.cpp
new file mode 100644 (file)
index 0000000..3a7c880
--- /dev/null
@@ -0,0 +1,751 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "layer_shell_v1.h"
+#include "xdg_shell_stable.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+
+using Vec2 = std::pair<int, int>;
+using Rect = std::pair<Vec2, Vec2>;
+
+namespace
+{
+class LayerSurfaceTest: public wlcs::StartedInProcessServer
+{
+public:
+    LayerSurfaceTest()
+        : client{the_server()},
+          surface{client},
+          layer_surface{client, surface}
+    {
+    }
+
+    void commit_and_wait_for_configure()
+    {
+        wl_surface_commit(surface);
+        layer_surface.dispatch_until_configure();
+    }
+
+    void expect_surface_is_at_position(std::pair<int, int> pos, wlcs::Surface const& expected)
+    {
+        auto pointer = the_server().create_pointer();
+        pointer.move_to(pos.first + 10, pos.second + 10);
+        client.roundtrip();
+
+        EXPECT_THAT(client.window_under_cursor(), Eq((wl_surface*)expected));
+        if (client.window_under_cursor() == expected)
+        {
+            EXPECT_THAT(wl_fixed_to_int(client.pointer_position().first), Eq(10));
+            EXPECT_THAT(wl_fixed_to_int(client.pointer_position().second), Eq(10));
+        }
+    }
+
+    void expect_surface_is_at_position(std::pair<int, int> pos)
+    {
+        expect_surface_is_at_position(pos, surface);
+    }
+
+    auto output_rect() const -> Rect
+    {
+        EXPECT_THAT(client.output_count(), Ge(1u)) << "There are no outputs to get a size from";
+        EXPECT_THAT(client.output_count(), Eq(1u)) << "Unclear which output the layer shell surface will be placed on";
+        auto output_state = client.output_state(0);
+        EXPECT_THAT((bool)output_state.mode_size, Eq(true)) << "Output has no size";
+        EXPECT_THAT((bool)output_state.geometry_position, Eq(true)) << "Output has no position";
+        auto size = output_state.mode_size.value();
+        if (output_state.scale)
+        {
+            size.first /= output_state.scale.value();
+            size.second /= output_state.scale.value();
+        }
+        return std::make_pair(output_state.geometry_position.value(), size);
+    }
+
+    auto configured_size() const -> Vec2
+    {
+        return std::make_pair(layer_surface.last_width(), layer_surface.last_height());
+    }
+
+    wlcs::Client client;
+    wlcs::Surface surface;
+    wlcs::LayerSurfaceV1 layer_surface;
+
+    int static const default_width = 200;
+    int static const default_height = 300;
+};
+
+struct LayerSurfaceLayout
+{
+    template<typename T>
+    struct Sides
+    {
+        T left, right, top, bottom;
+    };
+
+    static auto get_all() -> std::vector<LayerSurfaceLayout>
+    {
+        bool const range[]{false, true};
+        std::vector<LayerSurfaceLayout> result;
+        for (auto left: range)
+        {
+            for (auto right: range)
+            {
+                for (auto top: range)
+                {
+                    for (auto bottom: range)
+                    {
+                        result.emplace_back(Sides<bool>{left, right, top, bottom});
+                        result.emplace_back(Sides<bool>{left, right, top, bottom}, Sides<int>{6, 9, 12, 15});
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    LayerSurfaceLayout(Sides<bool> anchor)
+        : anchor{anchor},
+          margin{0, 0, 0, 0}
+    {
+    }
+
+    LayerSurfaceLayout(Sides<bool> anchor, Sides<int> margin)
+        : anchor{anchor},
+          margin{margin}
+    {
+    }
+
+    operator uint32_t() const
+    {
+        return
+            (anchor.left   ? ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT   : 0) |
+            (anchor.right  ? ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT  : 0) |
+            (anchor.top    ? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP    : 0) |
+            (anchor.bottom ? ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM : 0);
+    }
+
+    auto h_expand() const -> bool { return anchor.left && anchor.right; }
+    auto v_expand() const -> bool { return anchor.top && anchor.bottom; }
+
+    auto placement_rect(Rect const& output) const -> Rect
+    {
+        int const output_width = output.second.first;
+        int const output_height = output.second.second;
+        int const output_x = output.first.first;
+        int const output_y = output.first.second;
+        auto const config_size = configure_size(output);
+        int const width = config_size.first ? config_size.first : LayerSurfaceTest::default_width;
+        int const height = config_size.second ? config_size.second : LayerSurfaceTest::default_height;
+        int const x =
+            (anchor.left ?
+                output_x + margin.left :
+                (anchor.right ?
+                    (output_x + output_width - width - margin.right) :
+                    (output_x + (output_width - width) / 2)
+                )
+            );
+        int const y =
+            (anchor.top ?
+                output_y + margin.top :
+                (anchor.bottom ?
+                    (output_y + output_height - height - margin.bottom) :
+                    (output_y + (output_height - height) / 2)
+                )
+            );
+        return std::make_pair(std::make_pair(x, y), std::make_pair(width, height));;
+    }
+
+    auto request_size() const -> Vec2
+    {
+        return std::make_pair(
+            h_expand() ? 0 : LayerSurfaceTest::default_width,
+            v_expand() ? 0 : LayerSurfaceTest::default_height);
+    }
+
+    auto configure_size(Rect const& output) const -> Vec2
+    {
+        int const configure_width = h_expand() ? output.second.first - margin.left - margin.right : 0;
+        int const configure_height = v_expand() ? output.second.second - margin.top - margin.bottom : 0;
+        return std::make_pair(configure_width, configure_height);
+    }
+
+    // Will always either return 0, or a single enum value
+    auto attached_edge() const -> zwlr_layer_surface_v1_anchor
+    {
+        if (anchor.top == anchor.bottom)
+        {
+            if (anchor.left && !anchor.right)
+                return ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
+            else if (anchor.right && !anchor.left)
+                return ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
+        }
+        else if (anchor.left == anchor.right)
+        {
+            if (anchor.top && !anchor.bottom)
+                return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
+            else if (anchor.bottom && !anchor.top)
+                return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
+        }
+        return (zwlr_layer_surface_v1_anchor)0;
+    }
+
+    Sides<bool> const anchor;
+    Sides<int> const margin;
+};
+
+static void invoke_zwlr_layer_surface_v1_set_margin(
+    zwlr_layer_surface_v1* layer_surface,
+    LayerSurfaceLayout::Sides<int> const& margin)
+{
+    zwlr_layer_surface_v1_set_margin(layer_surface, margin.top, margin.right, margin.bottom, margin.left);
+}
+
+std::ostream& operator<<(std::ostream& os, const LayerSurfaceLayout& layout)
+{
+    std::vector<std::string> strs;
+    if (layout.anchor.left)
+        strs.emplace_back("left");
+    if (layout.anchor.right)
+        strs.emplace_back("right");
+    if (layout.anchor.top)
+        strs.emplace_back("top");
+    if (layout.anchor.bottom)
+        strs.emplace_back("bottom");
+    if (strs.empty())
+        strs.emplace_back("none");
+    os << "Anchor{";
+    for (auto i = 0u; i < strs.size(); i++)
+    {
+        if (i > 0)
+            os << " | ";
+        os << strs[i];
+    }
+    os << "}, Margin{";
+    if (layout.margin.left || layout.margin.right || layout.margin.top || layout.margin.bottom)
+    {
+        os << "l: " << layout.margin.left << ", ";
+        os << "r: " << layout.margin.right << ", ";
+        os << "t: " << layout.margin.top << ", ";
+        os << "b: " << layout.margin.bottom;
+    }
+    os << "}";
+    return os;
+}
+
+class LayerSurfaceLayoutTest:
+    public LayerSurfaceTest,
+    public testing::WithParamInterface<LayerSurfaceLayout>
+{
+};
+
+struct LayerLayerParams
+{
+    std::experimental::optional<zwlr_layer_shell_v1_layer> below;
+    std::experimental::optional<zwlr_layer_shell_v1_layer> above;
+};
+
+std::ostream& operator<<(std::ostream& os, const LayerLayerParams& layer)
+{
+    os << "layers:";
+    for (auto const& i : {layer.below, layer.above})
+    {
+        os << " ";
+        os << "Layer{";
+        if (i)
+        {
+            switch (auto const value = i.value())
+            {
+            case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND:
+                os << "background";
+                break;
+            case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM:
+                os << "bottom";
+                break;
+            case ZWLR_LAYER_SHELL_V1_LAYER_TOP:
+                os << "top";
+                break;
+            case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY:
+                os << "overlay";
+                break;
+            default:
+                os << "INVALID(" << value << ")";
+            }
+        }
+        else
+        {
+            os << "none";
+        }
+    }
+    return os;
+}
+
+class LayerSurfaceLayerTest:
+    public wlcs::StartedInProcessServer,
+    public testing::WithParamInterface<LayerLayerParams>
+{
+public:
+    struct SurfaceOnLayer
+    {
+        SurfaceOnLayer(
+            wlcs::Client& client,
+            std::experimental::optional<zwlr_layer_shell_v1_layer> layer)
+            : surface{layer ? wlcs::Surface{client} : client.create_visible_surface(width, height)}
+        {
+            if (layer)
+            {
+                layer_surface.emplace(client, surface, layer.value());
+                zwlr_layer_surface_v1_set_size(layer_surface.value(), width, height);
+                surface.attach_visible_buffer(width, height);
+            }
+        }
+
+        wlcs::Surface surface;
+        std::experimental::optional<wlcs::LayerSurfaceV1> layer_surface;
+    };
+
+    LayerSurfaceLayerTest()
+        : client(the_server())
+    {
+    }
+
+    static const int width{200}, height{100};
+    wlcs::Client client;
+};
+
+struct SizeAndAnchors
+{
+    uint32_t width, height;
+    LayerSurfaceLayout anchors;
+};
+
+class LayerSurfaceErrorsTest:
+    public LayerSurfaceTest,
+    public testing::WithParamInterface<SizeAndAnchors>
+{
+};
+
+}
+
+TEST_F(LayerSurfaceTest, specifying_no_size_without_anchors_is_an_error)
+{
+    try
+    {
+        // Protocol specifies that a size of (0,0) is the default
+        commit_and_wait_for_configure();
+    }
+    catch (wlcs::ProtocolError const& err)
+    {
+        EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface));
+        // The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate
+        EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE));
+        return;
+    }
+    FAIL() << "Expected protocol error not raised";
+}
+
+TEST_F(LayerSurfaceTest, specifying_zero_size_without_anchors_is_an_error)
+{
+    try
+    {
+        zwlr_layer_surface_v1_set_size(layer_surface, 0, 0);
+        commit_and_wait_for_configure();
+    }
+    catch (wlcs::ProtocolError const& err)
+    {
+        EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface));
+        // The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate
+        EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE));
+        return;
+    }
+    FAIL() << "Expected protocol error not raised";
+}
+
+TEST_P(LayerSurfaceErrorsTest, specifying_zero_size_without_corresponding_anchors_is_an_error)
+{
+    try
+    {
+        zwlr_layer_surface_v1_set_size(layer_surface, GetParam().width, GetParam().height);
+        zwlr_layer_surface_v1_set_anchor(layer_surface, GetParam().anchors);
+        commit_and_wait_for_configure();
+    }
+    catch (wlcs::ProtocolError const& err)
+    {
+        EXPECT_THAT(err.interface(), Eq(&zwlr_layer_surface_v1_interface));
+        // The protocol does not explicitly state what error to send here; INVALID_SIZE seems most appropriate
+        EXPECT_THAT(err.error_code(), Eq(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE));
+        return;
+    }
+    FAIL() << "Expected protocol error not raised";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Anchors,
+    LayerSurfaceErrorsTest,
+    testing::Values(
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, false, false}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, false, false}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, false, false}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, false, false}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, true, false}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, true, false}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, true, false}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, true, false}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, false, false, true}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, false, false, true}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, false, true}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{true, true, false, true}}},
+        SizeAndAnchors{0, 0, LayerSurfaceLayout{{false, true, true, true}}},
+        SizeAndAnchors{200, 0, LayerSurfaceLayout{{false, false, false, true}}},
+        SizeAndAnchors{200, 0, LayerSurfaceLayout{{false, false, true, false}}},
+        SizeAndAnchors{0, 200, LayerSurfaceLayout{{true, false, false, true}}},
+        SizeAndAnchors{0, 200, LayerSurfaceLayout{{false, true, true, false}}}
+    ));
+
+TEST_F(LayerSurfaceTest, can_open_layer_surface)
+{
+    zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
+    commit_and_wait_for_configure();
+}
+
+TEST_F(LayerSurfaceTest, gets_configured_with_supplied_size_when_set)
+{
+    int width = 123, height = 546;
+    zwlr_layer_surface_v1_set_size(layer_surface, width, height);
+    commit_and_wait_for_configure();
+    EXPECT_THAT(configured_size(), Eq(std::make_pair(width, height)));
+}
+
+TEST_F(LayerSurfaceTest, gets_configured_with_supplied_size_even_when_anchored_to_edges)
+{
+    int width = 321, height = 218;
+    zwlr_layer_surface_v1_set_anchor(layer_surface, LayerSurfaceLayout({true, true, true, true}));
+    zwlr_layer_surface_v1_set_size(layer_surface, width, height);
+    commit_and_wait_for_configure();
+    EXPECT_THAT(configured_size(), Eq(std::make_pair(width, height)));
+}
+
+TEST_F(LayerSurfaceTest, when_anchored_to_all_edges_gets_configured_with_output_size)
+{
+    zwlr_layer_surface_v1_set_anchor(layer_surface, LayerSurfaceLayout({true, true, true, true}));
+    commit_and_wait_for_configure();
+    auto const size = output_rect().second;
+    ASSERT_THAT(configured_size(), Eq(size));
+}
+
+TEST_F(LayerSurfaceTest, gets_configured_after_anchor_change)
+{
+    zwlr_layer_surface_v1_set_size(layer_surface, default_width, default_height);
+    commit_and_wait_for_configure();
+    zwlr_layer_surface_v1_set_size(layer_surface, 0, 0);
+    zwlr_layer_surface_v1_set_anchor(layer_surface, LayerSurfaceLayout({true, true, true, true}));
+    commit_and_wait_for_configure();
+    EXPECT_THAT(configured_size().first, Gt(0));
+    EXPECT_THAT(configured_size().second, Gt(0));
+}
+
+TEST_P(LayerSurfaceLayoutTest, is_initially_positioned_correctly_for_anchor)
+{
+    auto const layout = GetParam();
+    auto const output = output_rect();
+    auto const rect = layout.placement_rect(output);
+    auto const request_size = layout.request_size();
+
+    zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
+    invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
+    zwlr_layer_surface_v1_set_size(layer_surface, request_size.first, request_size.second);
+    commit_and_wait_for_configure();
+
+    auto configure_size = layout.configure_size(output);
+    if (configure_size.first)
+    {
+        EXPECT_THAT(configured_size().first, Eq(configure_size.first));
+    }
+    if (configure_size.second)
+    {
+        EXPECT_THAT(configured_size().second, Eq(configure_size.second));
+    }
+
+    surface.attach_visible_buffer(rect.second.first, rect.second.second);
+    expect_surface_is_at_position(rect.first);
+}
+
+TEST_P(LayerSurfaceLayoutTest, is_positioned_correctly_when_explicit_size_does_not_match_buffer_size)
+{
+    auto const layout = GetParam();
+    int const initial_width{52}, initial_height{74};
+    auto const rect = layout.placement_rect(output_rect());
+    auto const request_size = layout.request_size();
+
+    zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
+    invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
+    zwlr_layer_surface_v1_set_size(layer_surface, request_size.first, request_size.second);
+    commit_and_wait_for_configure();
+
+    surface.attach_visible_buffer(initial_width, initial_height);
+
+    expect_surface_is_at_position(rect.first);
+}
+
+TEST_P(LayerSurfaceLayoutTest, is_positioned_correctly_when_layout_changed)
+{
+    auto const layout = GetParam();
+    auto const output = output_rect();
+    auto const result_rect = layout.placement_rect(output);
+    auto const request_size = layout.request_size();
+
+    zwlr_layer_surface_v1_set_size(layer_surface, default_width - 5, default_height - 2);
+    commit_and_wait_for_configure();
+
+    zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
+    invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
+    zwlr_layer_surface_v1_set_size(layer_surface, request_size.first, request_size.second);
+    surface.attach_visible_buffer(result_rect.second.first, result_rect.second.second);
+    wl_surface_commit(surface);
+    client.roundtrip(); // Sometimes we get a configure, sometimes we don't
+
+    auto configure_size = layout.configure_size(output);
+    if (configure_size.first)
+    {
+        EXPECT_THAT(configured_size().first, Eq(configure_size.first));
+    }
+    if (configure_size.second)
+    {
+        EXPECT_THAT(configured_size().second, Eq(configure_size.second));
+    }
+    expect_surface_is_at_position(result_rect.first);
+}
+
+TEST_P(LayerSurfaceLayoutTest, maximized_xdg_toplevel_is_shrunk_for_exclusive_zone)
+{
+    int const exclusive_zone = 25;
+    int width = 0, height = 0;
+    wlcs::Surface other_surface{client};
+    wlcs::XdgSurfaceStable xdg_surface{client, other_surface};
+    wlcs::XdgToplevelStable toplevel{xdg_surface};
+    toplevel.add_configure_notification([&](int32_t w, int32_t h, wl_array* /*states*/)
+        {
+            if (w != width || h != height)
+            {
+                width = w;
+                height = h;
+                if (w == 0)
+                    w = 100;
+                if (h == 0)
+                    h = 150;
+                other_surface.attach_buffer(w, h);
+                xdg_surface_set_window_geometry(xdg_surface, 0, 0, w, h);
+            }
+        });
+    xdg_surface.add_configure_notification([&](uint32_t serial)
+        {
+            xdg_surface_ack_configure(xdg_surface, serial);
+            wl_surface_commit(other_surface);
+        });
+    xdg_toplevel_set_maximized(toplevel);
+    wl_surface_commit(other_surface);
+    client.dispatch_until([&](){ return width > 0; });
+
+    int const initial_width = width;
+    int const initial_height = height;
+
+    ASSERT_THAT(initial_width, Gt(0)) << "Can't test as shell did not configure XDG surface with a size";
+    ASSERT_THAT(initial_height, Gt(0)) << "Can't test as shell did not configure XDG surface with a size";
+
+    auto const layout = GetParam();
+    auto const request_size = layout.request_size();
+    auto const rect = layout.placement_rect(output_rect());
+    zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
+    invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
+    zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, exclusive_zone);
+    zwlr_layer_surface_v1_set_size(layer_surface, request_size.first, request_size.second);
+    commit_and_wait_for_configure();
+    surface.attach_visible_buffer(rect.second.first, rect.second.second);
+
+    int const new_width = width;
+    int const new_height = height;
+
+    int expected_width = initial_width;
+    int expected_height = initial_height;
+
+    switch (layout.attached_edge())
+    {
+    case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT:
+        expected_width -= exclusive_zone + layout.margin.left;
+        break;
+
+    case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT:
+        expected_width -= exclusive_zone + layout.margin.right;
+        break;
+
+    case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP:
+        expected_height -= exclusive_zone + layout.margin.top;
+        break;
+
+    case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM:
+        expected_height -= exclusive_zone + layout.margin.bottom;
+        break;
+
+    default: ;
+    }
+
+    EXPECT_THAT(new_width, Eq(expected_width));
+    EXPECT_THAT(new_height, Eq(expected_height));
+}
+
+TEST_P(LayerSurfaceLayoutTest, simple_popup_positioned_correctly)
+{
+    auto const layout = GetParam();
+    auto const output = output_rect();
+    auto const layer_surface_rect = layout.placement_rect(output);
+    auto const layer_surface_request_size = layout.request_size();
+
+    zwlr_layer_surface_v1_set_anchor(layer_surface, layout);
+    invoke_zwlr_layer_surface_v1_set_margin(layer_surface, layout.margin);
+    zwlr_layer_surface_v1_set_size(layer_surface, layer_surface_request_size.first, layer_surface_request_size.second);
+    commit_and_wait_for_configure();
+    surface.attach_visible_buffer(layer_surface_rect.second.first, layer_surface_rect.second.second);
+
+    auto const popup_size = std::make_pair(30, 30);
+    wlcs::XdgPositionerStable positioner{client};
+    xdg_positioner_set_size(positioner, popup_size.first, popup_size.second);
+    xdg_positioner_set_anchor_rect(
+        positioner,
+        5, 5,
+        layer_surface_rect.second.first - 10, layer_surface_rect.second.second - 10);
+    xdg_positioner_set_anchor(positioner,  0);
+    xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+
+    wlcs::Surface popup_wl_surface{client};
+    wlcs::XdgSurfaceStable popup_xdg_surface{client, popup_wl_surface};
+    wlcs::XdgPopupStable popup_xdg_popup{popup_xdg_surface, std::experimental::nullopt, positioner};
+    zwlr_layer_surface_v1_get_popup(layer_surface, popup_xdg_popup);
+
+    int popup_surface_configure_count = 0;
+    Vec2 popup_configured_position;
+    popup_xdg_surface.add_configure_notification([&](uint32_t serial)
+        {
+            xdg_surface_ack_configure(popup_xdg_surface, serial);
+            popup_surface_configure_count++;
+        });
+    popup_xdg_popup.add_configure_notification([&](int32_t x, int32_t y, int32_t, int32_t)
+        {
+            popup_configured_position.first = x;
+            popup_configured_position.second = y;
+        });
+
+    popup_wl_surface.attach_visible_buffer(popup_size.first, popup_size.second);
+    client.dispatch_until([&](){ return popup_surface_configure_count > 0; });
+
+    EXPECT_THAT(
+        popup_configured_position,
+        Eq(Vec2{layer_surface_rect.second.first / 2, layer_surface_rect.second.second / 2}));
+
+    expect_surface_is_at_position(
+        Vec2{
+            layer_surface_rect.first.first + layer_surface_rect.second.first / 2,
+            layer_surface_rect.first.second + layer_surface_rect.second.second / 2},
+        popup_wl_surface);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Anchor,
+    LayerSurfaceLayoutTest,
+    testing::ValuesIn(LayerSurfaceLayout::get_all()));
+
+TEST_P(LayerSurfaceLayerTest, surface_on_lower_layer_is_initially_placed_below)
+{
+    auto const& param = GetParam();
+
+    SurfaceOnLayer above{client, param.above};
+    SurfaceOnLayer below{client, param.below};
+
+    the_server().move_surface_to(above.surface, 100, 0);
+    the_server().move_surface_to(below.surface, 0, 0);
+
+    auto pointer = the_server().create_pointer();
+    pointer.move_to(1, 1);
+    client.roundtrip();
+
+    ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)below.surface))
+        << "Test could not run because below surface was not detected when above surface was not in the way";
+
+    the_server().move_surface_to(above.surface, 0, 0);
+    the_server().move_surface_to(below.surface, 0, 0);
+
+    pointer.move_to(2, 3);
+    client.roundtrip();
+
+    ASSERT_THAT(client.window_under_cursor(), Ne((wl_surface*)below.surface))
+        << "Wrong wurface was on top";
+    ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)above.surface))
+        << "Correct surface was not on top";
+}
+
+TEST_P(LayerSurfaceLayerTest, below_surface_can_not_be_raised_with_click)
+{
+    auto const& param = GetParam();
+
+    SurfaceOnLayer above{client, param.above};
+    SurfaceOnLayer below{client, param.below};
+
+    the_server().move_surface_to(above.surface, width / 2, 0);
+    the_server().move_surface_to(below.surface, 0, 0);
+
+    auto pointer = the_server().create_pointer();
+    pointer.move_to(1, 1);
+    client.roundtrip();
+
+    ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)below.surface))
+        << "Test could not run because below surface was not detected and clicked on";
+
+    pointer.left_button_down();
+    client.roundtrip();
+    pointer.left_button_up();
+    client.roundtrip();
+    pointer.move_to(width / 2 + 2, 1);
+    client.roundtrip();
+
+    ASSERT_THAT(client.window_under_cursor(), Ne((wl_surface*)below.surface))
+        << "Wrong wurface was on top";
+    ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)above.surface))
+        << "Correct surface was not on top";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Layer,
+    LayerSurfaceLayerTest,
+    testing::Values(
+        LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM},
+        LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, ZWLR_LAYER_SHELL_V1_LAYER_TOP},
+        LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_TOP, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY},
+        LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY},
+        LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, ZWLR_LAYER_SHELL_V1_LAYER_TOP},
+        LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, std::experimental::nullopt},
+        LayerLayerParams{ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, std::experimental::nullopt},
+        LayerLayerParams{std::experimental::nullopt, ZWLR_LAYER_SHELL_V1_LAYER_TOP},
+        LayerLayerParams{std::experimental::nullopt, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY}
+    ));
+
+// TODO: test it gets put on a specified output
+// TODO: test margin
+// TODO: test keyboard interactivity
diff --git a/tests/xdg_output_v1.cpp b/tests/xdg_output_v1.cpp
new file mode 100644 (file)
index 0000000..9b1a187
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: William Wold <william.wold@canonical.com>
+ */
+
+#include "in_process_server.h"
+#include "xdg_output_v1.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using namespace wlcs;
+
+using XdgOutputV1Test = wlcs::InProcessServer;
+
+TEST_F(XdgOutputV1Test, xdg_output_properties_set)
+{
+    Client client{the_server()};
+    ASSERT_THAT(client.output_count(), Ge(1u));
+
+    XdgOutputManagerV1 xdg_output_manager{client};
+    XdgOutputV1 xdg_output{xdg_output_manager, 0};
+    client.roundtrip();
+
+    auto const& state = xdg_output.state();
+    ASSERT_THAT((bool)state.logical_position, true);
+    ASSERT_THAT((bool)state.logical_size, true);
+    ASSERT_THAT((bool)state.name, true);
+    // Description is optional
+}
diff --git a/tests/xdg_popup.cpp b/tests/xdg_popup.cpp
new file mode 100644 (file)
index 0000000..d7b7ab5
--- /dev/null
@@ -0,0 +1,757 @@
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+#include "xdg_shell_v6.h"
+#include "layer_shell_v1.h"
+
+#include <gmock/gmock.h>
+
+#include <experimental/optional>
+
+using namespace testing;
+
+int const window_width = 400, window_height = 500;
+int const popup_width = 60, popup_height = 40;
+
+namespace
+{
+uint32_t anchor_stable_to_v6(xdg_positioner_anchor anchor)
+{
+    switch (anchor)
+    {
+        case XDG_POSITIONER_ANCHOR_NONE:
+            return ZXDG_POSITIONER_V6_ANCHOR_NONE;
+        case XDG_POSITIONER_ANCHOR_TOP:
+            return ZXDG_POSITIONER_V6_ANCHOR_TOP;
+        case XDG_POSITIONER_ANCHOR_BOTTOM:
+            return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
+        case XDG_POSITIONER_ANCHOR_LEFT:
+            return ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+        case XDG_POSITIONER_ANCHOR_RIGHT:
+            return ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+        case XDG_POSITIONER_ANCHOR_TOP_LEFT:
+            return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+        case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT:
+            return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+        case XDG_POSITIONER_ANCHOR_TOP_RIGHT:
+            return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+        case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT:
+            return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+        default:
+            return ZXDG_POSITIONER_V6_ANCHOR_NONE;
+    }
+}
+
+uint32_t gravity_stable_to_v6(xdg_positioner_gravity gravity)
+{
+    switch (gravity)
+    {
+        case XDG_POSITIONER_GRAVITY_NONE:
+            return ZXDG_POSITIONER_V6_GRAVITY_NONE;
+        case XDG_POSITIONER_GRAVITY_TOP:
+            return ZXDG_POSITIONER_V6_GRAVITY_TOP;
+        case XDG_POSITIONER_GRAVITY_BOTTOM:
+            return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
+        case XDG_POSITIONER_GRAVITY_LEFT:
+            return ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+        case XDG_POSITIONER_GRAVITY_RIGHT:
+            return ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+        case XDG_POSITIONER_GRAVITY_TOP_LEFT:
+            return ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+        case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT:
+            return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+        case XDG_POSITIONER_GRAVITY_TOP_RIGHT:
+            return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+        case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT:
+            return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+        default:
+            return ZXDG_POSITIONER_V6_GRAVITY_NONE;
+    }
+}
+
+uint32_t constraint_adjustment_stable_to_v6(xdg_positioner_constraint_adjustment ca)
+{
+    return ca; // the two enums have the same values
+}
+
+struct PositionerParams
+{
+    PositionerParams()
+        : popup_size{popup_width, popup_height},
+          anchor_rect{{0, 0}, {window_width, window_height}}
+    {
+    }
+
+    auto with_size(int x, int y) -> PositionerParams& { popup_size = {x, y}; return *this; }
+    auto with_anchor_rect(int x, int y, int w, int h) -> PositionerParams& { anchor_rect = {{x, y}, {w, h}}; return *this; }
+    auto with_anchor(xdg_positioner_anchor value) -> PositionerParams& { anchor_stable = {value}; return *this; }
+    auto with_gravity(xdg_positioner_gravity value) -> PositionerParams& { gravity_stable = {value}; return *this; }
+    auto with_constraint_adjustment(xdg_positioner_constraint_adjustment value) -> PositionerParams& { constraint_adjustment_stable = {value}; return *this; }
+    auto with_offset(int x, int y) -> PositionerParams& { offset = {{x, y}}; return *this; }
+
+    std::pair<int, int> popup_size; // will default to XdgPopupStableTestBase::popup_(width|height) if nullopt
+    std::pair<std::pair<int, int>, std::pair<int, int>> anchor_rect; // will default to the full window rect
+    std::experimental::optional<xdg_positioner_anchor> anchor_stable;
+    std::experimental::optional<xdg_positioner_gravity> gravity_stable;
+    std::experimental::optional<xdg_positioner_constraint_adjustment> constraint_adjustment_stable;
+    std::experimental::optional<std::pair<int, int>> offset;
+};
+
+struct PositionerTestParams
+{
+    PositionerTestParams(std::string name, int expected_x, int expected_y, PositionerParams const& positioner)
+        : name{name},
+          expected_positon{expected_x, expected_y},
+          positioner{positioner}
+    {
+    }
+
+    std::string name;
+    std::pair<int, int> expected_positon;
+    PositionerParams positioner;
+};
+
+std::ostream& operator<<(std::ostream& out, PositionerTestParams const& param)
+{
+    return out << param.name;
+}
+
+class XdgPopupManagerBase
+{
+public:
+    struct State
+    {
+        int x;
+        int y;
+        int width;
+        int height;
+    };
+
+    static int const window_x = 500, window_y = 500;
+
+    XdgPopupManagerBase(wlcs::InProcessServer* const in_process_server)
+        : the_server{in_process_server->the_server()},
+          client{the_server},
+          surface{client}
+    {
+        surface.add_frame_callback([this](auto) { surface_rendered = true; });
+    }
+
+    virtual ~XdgPopupManagerBase() = default;
+
+    void wait_for_frame_to_render()
+    {
+        surface.attach_buffer(window_width, window_height);
+        surface_rendered = false;
+        wl_surface_commit(surface);
+        client.dispatch_until([this]() { return surface_rendered; });
+        the_server.move_surface_to(surface, window_x, window_y);
+    }
+
+    void map_popup(PositionerParams const& params)
+    {
+        popup_surface.emplace(client);
+        setup_popup(params);
+        wl_surface_commit(popup_surface.value());
+        dispatch_until_popup_configure();
+        popup_surface.value().attach_buffer(popup_width, popup_height);
+        bool surface_rendered{false};
+        popup_surface.value().add_frame_callback([&surface_rendered](auto) { surface_rendered = true; });
+        wl_surface_commit(popup_surface.value());
+        client.dispatch_until([&surface_rendered]() { return surface_rendered; });
+    }
+
+    void unmap_popup()
+    {
+        clear_popup();
+        popup_surface = std::experimental::nullopt;
+    }
+
+    virtual auto popup_position() const -> std::experimental::optional<std::pair<int, int>> = 0;
+
+    virtual void dispatch_until_popup_configure() = 0;
+
+    wlcs::Server& the_server;
+
+    wlcs::Client client;
+    wlcs::Surface surface;
+    std::experimental::optional<wlcs::Surface> popup_surface;
+
+    std::experimental::optional<State> state;
+
+protected:
+    virtual void setup_popup(PositionerParams const& params) = 0;
+    virtual void clear_popup() = 0;
+
+    bool surface_rendered{true};
+};
+
+class XdgPopupStableManager : public XdgPopupManagerBase
+{
+public:
+    XdgPopupStableManager(wlcs::InProcessServer* const in_process_server)
+        : XdgPopupManagerBase{in_process_server},
+          xdg_shell_surface{client, surface},
+          toplevel{xdg_shell_surface}
+    {
+        wait_for_frame_to_render();
+    }
+
+    void dispatch_until_popup_configure() override
+    {
+        client.dispatch_until(
+            [prev_count = popup_surface_configure_count, &current_count = popup_surface_configure_count]()
+            {
+                return current_count > prev_count;
+            });
+    }
+
+    void setup_popup(PositionerParams const& param) override
+    {
+        wlcs::XdgPositionerStable positioner{client};
+
+        // size must always be set
+        xdg_positioner_set_size(positioner, param.popup_size.first, param.popup_size.second);
+
+        // anchor rect must always be set
+        xdg_positioner_set_anchor_rect(
+            positioner,
+            param.anchor_rect.first.first,
+            param.anchor_rect.first.second,
+            param.anchor_rect.second.first,
+            param.anchor_rect.second.second);
+
+        if (param.anchor_stable)
+            xdg_positioner_set_anchor(positioner,  param.anchor_stable.value());
+
+        if (param.gravity_stable)
+            xdg_positioner_set_gravity(positioner, param.gravity_stable.value());
+
+        if (param.constraint_adjustment_stable)
+            xdg_positioner_set_constraint_adjustment(positioner, param.constraint_adjustment_stable.value());
+
+        if (param.offset)
+            xdg_positioner_set_offset(positioner, param.offset.value().first, param.offset.value().second);
+
+
+        popup_xdg_surface.emplace(client, popup_surface.value());
+
+        popup.emplace(popup_xdg_surface.value(), &xdg_shell_surface, positioner);
+
+        popup_xdg_surface.value().add_configure_notification([&](uint32_t serial)
+            {
+                xdg_surface_ack_configure(popup_xdg_surface.value(), serial);
+                popup_surface_configure_count++;
+            });
+
+        popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height)
+            {
+                state = State{x, y, width, height};
+            });
+    }
+
+    void clear_popup() override
+    {
+        popup = std::experimental::nullopt;
+        popup_xdg_surface = std::experimental::nullopt;
+    }
+
+    auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override
+    {
+        return std::make_pair(state.value().x, state.value().y);
+    }
+
+    wlcs::XdgSurfaceStable xdg_shell_surface;
+    wlcs::XdgToplevelStable toplevel;
+
+    std::experimental::optional<wlcs::XdgSurfaceStable> popup_xdg_surface;
+    std::experimental::optional<wlcs::XdgPopupStable> popup;
+
+    int popup_surface_configure_count{0};
+};
+
+class XdgPopupV6Manager : public XdgPopupManagerBase
+{
+public:
+    XdgPopupV6Manager(wlcs::InProcessServer* const in_process_server)
+        : XdgPopupManagerBase{in_process_server},
+          xdg_shell_surface{client, surface},
+          toplevel{xdg_shell_surface}
+    {
+        wait_for_frame_to_render();
+    }
+
+    void dispatch_until_popup_configure() override
+    {
+        client.dispatch_until(
+            [prev_count = popup_surface_configure_count, &current_count = popup_surface_configure_count]()
+            {
+                return current_count > prev_count;
+            });
+    }
+
+    void setup_popup(PositionerParams const& param) override
+    {
+        wlcs::XdgPositionerV6 positioner{client};
+
+        // size must always be set
+        zxdg_positioner_v6_set_size(positioner, param.popup_size.first, param.popup_size.second);
+
+        // anchor rect must always be set
+        zxdg_positioner_v6_set_anchor_rect(
+            positioner,
+            param.anchor_rect.first.first,
+            param.anchor_rect.first.second,
+            param.anchor_rect.second.first,
+            param.anchor_rect.second.second);
+
+        if (param.anchor_stable)
+        {
+            uint32_t v6_anchor = anchor_stable_to_v6(param.anchor_stable.value());
+            zxdg_positioner_v6_set_anchor(positioner, v6_anchor);
+        }
+
+        if (param.gravity_stable)
+        {
+            uint32_t v6_gravity = gravity_stable_to_v6(param.gravity_stable.value());
+            zxdg_positioner_v6_set_gravity(positioner, v6_gravity);
+        }
+
+        if (param.constraint_adjustment_stable)
+        {
+            uint32_t v6_constraint_adjustment =
+                constraint_adjustment_stable_to_v6(param.constraint_adjustment_stable.value());
+            zxdg_positioner_v6_set_constraint_adjustment(positioner, v6_constraint_adjustment);
+        }
+
+        if (param.offset)
+        {
+            zxdg_positioner_v6_set_offset(positioner, param.offset.value().first, param.offset.value().second);
+        }
+
+
+        popup_xdg_surface.emplace(client, popup_surface.value());
+        popup.emplace(popup_xdg_surface.value(), xdg_shell_surface, positioner);
+
+        popup_xdg_surface.value().add_configure_notification([&](uint32_t serial)
+            {
+                zxdg_surface_v6_ack_configure(popup_xdg_surface.value(), serial);
+                popup_surface_configure_count++;
+            });
+
+        popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height)
+            {
+                state = State{x, y, width, height};
+            });
+    }
+
+    void clear_popup() override
+    {
+        popup = std::experimental::nullopt;
+        popup_xdg_surface = std::experimental::nullopt;
+    }
+
+    auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override
+    {
+        return std::make_pair(state.value().x, state.value().y);
+    }
+
+    wlcs::XdgSurfaceV6 xdg_shell_surface;
+    wlcs::XdgToplevelV6 toplevel;
+
+    std::experimental::optional<wlcs::XdgSurfaceV6> popup_xdg_surface;
+    std::experimental::optional<wlcs::XdgPopupV6> popup;
+
+    int popup_surface_configure_count{0};
+};
+
+class LayerV1PopupManager : public XdgPopupManagerBase
+{
+public:
+    LayerV1PopupManager(wlcs::InProcessServer* const in_process_server)
+        : XdgPopupManagerBase{in_process_server},
+          layer_surface{client, surface}
+    {
+        zwlr_layer_surface_v1_set_size(layer_surface, window_width, window_height);
+        wait_for_frame_to_render();
+    }
+
+    void dispatch_until_popup_configure() override
+    {
+        client.dispatch_until(
+            [prev_count = popup_surface_configure_count, &current_count = popup_surface_configure_count]()
+            {
+                return current_count > prev_count;
+            });
+    }
+
+    void setup_popup(PositionerParams const& param) override
+    {
+        wlcs::XdgPositionerStable positioner{client};
+
+        // size must always be set
+        xdg_positioner_set_size(positioner, param.popup_size.first, param.popup_size.second);
+
+        // anchor rect must always be set
+        xdg_positioner_set_anchor_rect(
+            positioner,
+            param.anchor_rect.first.first,
+            param.anchor_rect.first.second,
+            param.anchor_rect.second.first,
+            param.anchor_rect.second.second);
+
+        if (param.anchor_stable)
+            xdg_positioner_set_anchor(positioner,  param.anchor_stable.value());
+
+        if (param.gravity_stable)
+            xdg_positioner_set_gravity(positioner, param.gravity_stable.value());
+
+        if (param.constraint_adjustment_stable)
+            xdg_positioner_set_constraint_adjustment(positioner, param.constraint_adjustment_stable.value());
+
+        if (param.offset)
+            xdg_positioner_set_offset(positioner, param.offset.value().first, param.offset.value().second);
+
+
+        popup_xdg_surface.emplace(client, popup_surface.value());
+        popup.emplace(popup_xdg_surface.value(), std::experimental::nullopt, positioner);
+        zwlr_layer_surface_v1_get_popup(layer_surface, popup.value());
+
+        popup_xdg_surface.value().add_configure_notification([&](uint32_t serial)
+            {
+                xdg_surface_ack_configure(popup_xdg_surface.value(), serial);
+                popup_surface_configure_count++;
+            });
+
+        popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height)
+            {
+                state = State{x, y, width, height};
+            });
+    }
+
+    void clear_popup() override
+    {
+        popup = std::experimental::nullopt;
+        popup_xdg_surface = std::experimental::nullopt;
+    }
+
+    auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override
+    {
+        return std::make_pair(state.value().x, state.value().y);
+    }
+
+    wlcs::LayerSurfaceV1 layer_surface;
+
+    std::experimental::optional<wlcs::XdgSurfaceStable> popup_xdg_surface;
+    std::experimental::optional<wlcs::XdgPopupStable> popup;
+
+    int popup_surface_configure_count{0};
+};
+
+}
+
+class XdgPopupPositionerTest:
+    public wlcs::StartedInProcessServer,
+    public testing::WithParamInterface<PositionerTestParams>
+{
+};
+
+TEST_P(XdgPopupPositionerTest, xdg_shell_stable_popup_placed_correctly)
+{
+    auto manager = std::make_unique<XdgPopupStableManager>(this);
+    auto const& param = GetParam();
+
+    manager->map_popup(param.positioner);
+
+    ASSERT_THAT(
+        manager->popup_position(),
+        Ne(std::experimental::nullopt)) << "popup configure event not sent";
+
+    ASSERT_THAT(
+        manager->popup_position(),
+        Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position";
+}
+
+TEST_P(XdgPopupPositionerTest, xdg_shell_unstable_v6_popup_placed_correctly)
+{
+    auto manager = std::make_unique<XdgPopupV6Manager>(this);
+    auto const& param = GetParam();
+
+    manager->map_popup(param.positioner);
+
+    ASSERT_THAT(
+        manager->popup_position(),
+        Ne(std::experimental::nullopt)) << "popup configure event not sent";
+
+    ASSERT_THAT(
+        manager->popup_position(),
+        Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position";
+}
+
+TEST_P(XdgPopupPositionerTest, layer_shell_popup_placed_correctly)
+{
+    auto manager = std::make_unique<LayerV1PopupManager>(this);
+    auto const& param = GetParam();
+
+    manager->map_popup(param.positioner);
+
+    ASSERT_THAT(
+        manager->popup_position(),
+        Ne(std::experimental::nullopt)) << "popup configure event not sent";
+
+    ASSERT_THAT(
+        manager->popup_position(),
+        Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Default,
+    XdgPopupPositionerTest,
+    testing::Values(
+        PositionerTestParams{"default values", (window_width - popup_width) / 2, (window_height - popup_height) / 2, PositionerParams()}
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    Anchor,
+    XdgPopupPositionerTest,
+    testing::Values(
+        PositionerTestParams{"anchor left", -popup_width / 2, (window_height - popup_height) / 2,
+            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_LEFT)},
+
+        PositionerTestParams{"anchor right", window_width - popup_width / 2, (window_height - popup_height) / 2,
+            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_RIGHT)},
+
+        PositionerTestParams{"anchor top", (window_width - popup_width) / 2, -popup_height / 2,
+            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP)},
+
+        PositionerTestParams{"anchor bottom", (window_width - popup_width) / 2, window_height - popup_height / 2,
+            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM)},
+
+        PositionerTestParams{"anchor top left", -popup_width / 2, -popup_height / 2,
+            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)},
+
+        PositionerTestParams{"anchor top right", window_width - popup_width / 2, -popup_height / 2,
+            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT)},
+
+        PositionerTestParams{"anchor bottom left", -popup_width / 2, window_height - popup_height / 2,
+            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT)},
+
+        PositionerTestParams{"anchor bottom right", window_width - popup_width / 2, window_height - popup_height / 2,
+            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)}
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    Gravity,
+    XdgPopupPositionerTest,
+    testing::Values(
+        PositionerTestParams{"gravity none", (window_width - popup_width) / 2, (window_height - popup_height) / 2,
+            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_NONE)},
+
+        PositionerTestParams{"gravity left", window_width / 2 - popup_width, (window_height - popup_height) / 2,
+            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_LEFT)},
+
+        PositionerTestParams{"gravity right", window_width / 2, (window_height - popup_height) / 2,
+            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_RIGHT)},
+
+        PositionerTestParams{"gravity top", (window_width - popup_width) / 2, window_height / 2 - popup_height,
+            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP)},
+
+        PositionerTestParams{"gravity bottom", (window_width - popup_width) / 2, window_height / 2,
+            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM)},
+
+        PositionerTestParams{"gravity top left", window_width / 2 - popup_width, window_height / 2 - popup_height,
+            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)},
+
+        PositionerTestParams{"gravity top right", window_width / 2, window_height / 2 - popup_height,
+            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT)},
+
+        PositionerTestParams{"gravity bottom left", window_width / 2 - popup_width, window_height / 2,
+            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT)},
+
+        PositionerTestParams{"gravity bottom right", window_width / 2, window_height / 2,
+            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)}
+    ));
+
+INSTANTIATE_TEST_SUITE_P(
+    AnchorRect,
+    XdgPopupPositionerTest,
+    testing::Values(
+        PositionerTestParams{"explicit defaultPositionerParams anchor rect", (window_width - popup_width) / 2, (window_height - popup_height) / 2,
+            PositionerParams().with_anchor_rect(0, 0, window_width, window_height)},
+
+        PositionerTestParams{"upper left anchor rect", (window_width - 40 - popup_width) / 2, (window_height - 30 - popup_height) / 2,
+            PositionerParams().with_anchor_rect(0, 0, window_width - 40, window_height - 30)},
+
+        PositionerTestParams{"upper right anchor rect", (window_width + 40 - popup_width) / 2, (window_height - 30 - popup_height) / 2,
+            PositionerParams().with_anchor_rect(40, 0, window_width - 40, window_height - 30)},
+
+        PositionerTestParams{"lower left anchor rect", (window_width - 40 - popup_width) / 2, (window_height + 30 - popup_height) / 2,
+            PositionerParams().with_anchor_rect(0, 30, window_width - 40, window_height - 30)},
+
+        PositionerTestParams{"lower right anchor rect", (window_width + 40 - popup_width) / 2, (window_height + 30 - popup_height) / 2,
+            PositionerParams().with_anchor_rect(40, 30, window_width - 40, window_height - 30)},
+
+        PositionerTestParams{"offset anchor rect", (window_width - 40 - popup_width) / 2, (window_height - 80 - popup_height) / 2,
+            PositionerParams().with_anchor_rect(20, 20, window_width - 80, window_height - 120)}
+    ));
+
+struct XdgPopupTestParam
+{
+    std::function<std::unique_ptr<XdgPopupManagerBase>(wlcs::InProcessServer* const)> build;
+};
+
+std::ostream& operator<<(std::ostream& out, XdgPopupTestParam const&) { return out; }
+
+class XdgPopupTest:
+    public wlcs::StartedInProcessServer,
+    public testing::WithParamInterface<XdgPopupTestParam>
+{
+};
+
+TEST_P(XdgPopupTest, pointer_focus_goes_to_popup)
+{
+    auto const& param = GetParam();
+    auto manager = param.build(this);
+    auto pointer = the_server().create_pointer();
+    pointer.move_to(manager->window_x + 1, manager->window_y + 1);
+    manager->client.roundtrip();
+
+    EXPECT_THAT(manager->client.window_under_cursor(), Eq((wl_surface*)manager->surface));
+
+    auto positioner = PositionerParams{}
+        .with_size(30, 30)
+        .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
+        .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+    manager->map_popup(positioner);
+    manager->client.roundtrip();
+
+    pointer.move_to(manager->window_x + 2, manager->window_y + 1);
+    manager->client.roundtrip();
+
+    EXPECT_THAT(manager->client.window_under_cursor(), Eq((wl_surface*)manager->popup_surface.value()));
+}
+
+TEST_P(XdgPopupTest, popup_gives_up_pointer_focus_when_gone)
+{
+    auto const& param = GetParam();
+    auto manager = param.build(this);
+    auto pointer = the_server().create_pointer();
+
+    auto positioner = PositionerParams{}
+        .with_size(30, 30)
+        .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
+        .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+    manager->map_popup(positioner);
+    manager->client.roundtrip();
+
+    pointer.move_to(manager->window_x + 2, manager->window_y + 1);
+    manager->client.roundtrip();
+
+    EXPECT_THAT(manager->client.window_under_cursor(), Eq((wl_surface*)manager->popup_surface.value()));
+
+    manager->unmap_popup();
+    manager->client.roundtrip();
+    pointer.move_to(manager->window_x + 3, manager->window_y + 1);
+    manager->client.roundtrip();
+
+    EXPECT_THAT(manager->client.window_under_cursor(), Eq((wl_surface*)manager->surface));
+}
+
+TEST_F(XdgPopupTest, zero_size_anchor_rect_stable)
+{
+    auto manager = std::make_unique<XdgPopupStableManager>(this);
+
+    auto positioner = PositionerParams{}
+        .with_anchor_rect(window_width / 2, window_height / 2, 0, 0);
+
+    manager->map_popup(positioner);
+    manager->client.roundtrip();
+
+    ASSERT_THAT(
+        manager->popup_position(),
+        Eq(std::experimental::make_optional(
+            std::make_pair(
+                (window_width - popup_width) / 2,
+                (window_height - popup_height) / 2)))) << "popup placed in incorrect position";
+}
+
+// regression test for https://github.com/MirServer/mir/issues/836
+TEST_P(XdgPopupTest, popup_configure_is_valid)
+{
+    auto const& param = GetParam();
+    auto manager = param.build(this);
+
+    auto positioner = PositionerParams{}
+        .with_size(30, 30)
+        .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
+        .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+    manager->map_popup(positioner);
+    manager->client.roundtrip();
+
+    ASSERT_THAT(manager->state, Ne(std::experimental::nullopt));
+    EXPECT_THAT(manager->state.value().width, Gt(0));
+    EXPECT_THAT(manager->state.value().height, Gt(0));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    XdgPopupStable,
+    XdgPopupTest,
+    testing::Values(XdgPopupTestParam{
+        [](wlcs::InProcessServer* const server)
+        {
+            return std::make_unique<XdgPopupStableManager>(server);
+        }}));
+
+INSTANTIATE_TEST_SUITE_P(
+    XdgPopupUnstableV6,
+    XdgPopupTest,
+    testing::Values(XdgPopupTestParam{
+        [](wlcs::InProcessServer* const server)
+        {
+            return std::make_unique<XdgPopupV6Manager>(server);
+        }}));
+
+INSTANTIATE_TEST_SUITE_P(
+    LayerShellPopup,
+    XdgPopupTest,
+    testing::Values(XdgPopupTestParam{
+        [](wlcs::InProcessServer* const server)
+        {
+            return std::make_unique<LayerV1PopupManager>(server);
+        }}));
+
+// TODO: test that positioner is always overlapping or adjacent to parent
+// TODO: test that positioner is copied immediately after use
+// TODO: test that error is raised when incomplete positioner is used (positioner without size and anchor rect set)
+// TODO: test set_size
+// TODO: test that set_window_geometry affects anchor rect
+// TODO: test set_constraint_adjustment
+// TODO: test set_offset
+// TODO: test that a zero size anchor rect fails on v6
diff --git a/tests/xdg_surface_stable.cpp b/tests/xdg_surface_stable.cpp
new file mode 100644 (file)
index 0000000..d6ea0cf
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+#include "wl_handle.h"
+#include "version_specifier.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using XdgSurfaceStableTest = wlcs::InProcessServer;
+using wlcs::AnyVersion;
+
+TEST_F(XdgSurfaceStableTest, supports_xdg_shell_stable_protocol)
+{
+    wlcs::Client client{the_server()};
+    ASSERT_THAT(client.xdg_shell_stable(), NotNull());
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceStable xdg_surface{client, surface};
+}
+
+TEST_F(XdgSurfaceStableTest, gets_configure_event)
+{
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceStable xdg_surface{client, surface};
+
+    int surface_configure_count{0};
+    xdg_surface.add_configure_notification([&](uint32_t serial)
+        {
+            xdg_surface_ack_configure(xdg_surface, serial);
+            surface_configure_count++;
+        });
+
+    wlcs::XdgToplevelStable toplevel{xdg_surface};
+    surface.attach_buffer(600, 400);
+
+    client.roundtrip();
+
+    EXPECT_THAT(surface_configure_count, Eq(1));
+}
+
+TEST_F(XdgSurfaceStableTest, creating_xdg_surface_from_wl_surface_with_existing_role_is_an_error)
+{
+    wlcs::Client client{the_server()};
+
+    auto const xdg_wm_base = client.bind_if_supported<struct xdg_wm_base>(AnyVersion);
+
+    // We need a parent for the subsurface
+    auto const parent = client.create_visible_surface(300, 300);
+
+    auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor()));
+
+    // We need some way of assigning a role to a wl_surface. wl_subcompositor is as good a way as any.
+    auto const subsurface =
+        wlcs::wrap_wl_object(wl_subcompositor_get_subsurface(client.subcompositor(), surface, parent));
+
+    client.roundtrip();
+
+    try
+    {
+        xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+        client.roundtrip();
+    }
+    catch(wlcs::ProtocolError const& error)
+    {
+        EXPECT_THAT(error.interface(), Eq(&xdg_wm_base_interface));
+        EXPECT_THAT(error.error_code(), Eq(XDG_WM_BASE_ERROR_ROLE));
+        return;
+    }
+
+    FAIL() << "Expected protocol error not received";
+}
+
+
+TEST_F(XdgSurfaceStableTest, creating_xdg_surface_from_wl_surface_with_attached_buffer_is_an_error)
+{
+    wlcs::Client client{the_server()};
+
+    auto const xdg_wm_base = client.bind_if_supported<struct xdg_wm_base>(AnyVersion);
+
+    auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor()));
+    wlcs::ShmBuffer buffer{client, 300, 300};
+    wl_surface_attach(surface, buffer, 0, 0);
+    client.roundtrip();
+
+    try
+    {
+        xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+        client.roundtrip();
+    }
+    catch(wlcs::ProtocolError const& error)
+    {
+        EXPECT_THAT(error.interface(), Eq(&xdg_wm_base_interface));
+        EXPECT_THAT(error.error_code(), Eq(XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE));
+        return;
+    }
+
+    FAIL() << "Expected protocol error not received";
+}
+
+TEST_F(XdgSurfaceStableTest, creating_xdg_surface_from_wl_surface_with_committed_buffer_is_an_error)
+{
+    wlcs::Client client{the_server()};
+
+    auto const xdg_wm_base = client.bind_if_supported<struct xdg_wm_base>(AnyVersion);
+
+    auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor()));
+    wlcs::ShmBuffer buffer{client, 300, 300};
+    wl_surface_attach(surface, buffer, 0, 0);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    try
+    {
+        xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+        client.roundtrip();
+    }
+    catch(wlcs::ProtocolError const& error)
+    {
+        EXPECT_THAT(error.interface(), Eq(&xdg_wm_base_interface));
+        EXPECT_THAT(error.error_code(), Eq(XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE));
+        return;
+    }
+
+    FAIL() << "Expected protocol error not received";
+}
+
+TEST_F(XdgSurfaceStableTest, attaching_buffer_to_unconfigured_xdg_surface_is_an_error)
+{
+    wlcs::Client client{the_server()};
+
+    auto const xdg_wm_base = client.bind_if_supported<struct xdg_wm_base>(AnyVersion);
+
+    auto const surface = wlcs::wrap_wl_object(wl_compositor_create_surface(client.compositor()));
+    wlcs::ShmBuffer buffer{client, 300, 300};
+    client.roundtrip();
+
+    try
+    {
+        xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+        wl_surface_attach(surface, buffer, 0, 0);
+        client.roundtrip();
+    }
+    catch(wlcs::ProtocolError const& error)
+    {
+        EXPECT_THAT(error.interface(), Eq(&xdg_surface_interface));
+        EXPECT_THAT(error.error_code(), Eq(XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER));
+        return;
+    }
+
+    FAIL() << "Expected protocol error not received";
+}
diff --git a/tests/xdg_surface_v6.cpp b/tests/xdg_surface_v6.cpp
new file mode 100644 (file)
index 0000000..0821959
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_v6.h"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+using XdgSurfaceV6Test = wlcs::InProcessServer;
+
+TEST_F(XdgSurfaceV6Test, supports_xdg_shell_v6_protocol)
+{
+    wlcs::Client client{the_server()};
+    ASSERT_THAT(client.xdg_shell_v6(), NotNull());
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceV6 xdg_surface{client, surface};
+}
+
+TEST_F(XdgSurfaceV6Test, gets_configure_event)
+{
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceV6 xdg_surface{client, surface};
+
+    int surface_configure_count{0};
+    xdg_surface.add_configure_notification([&](uint32_t serial)
+        {
+            zxdg_surface_v6_ack_configure(xdg_surface, serial);
+            surface_configure_count++;
+        });
+
+    wlcs::XdgToplevelV6 toplevel{xdg_surface};
+    surface.attach_buffer(600, 400);
+
+    client.roundtrip();
+
+    EXPECT_THAT(surface_configure_count, Eq(1));
+}
diff --git a/tests/xdg_toplevel_stable.cpp b/tests/xdg_toplevel_stable.cpp
new file mode 100644 (file)
index 0000000..837eae8
--- /dev/null
@@ -0,0 +1,563 @@
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_stable.h"
+
+#include <gmock/gmock.h>
+
+#include <experimental/optional>
+
+using namespace testing;
+
+namespace
+{
+class ConfigurationWindow
+{
+public:
+    int const window_width = 200, window_height = 320;
+
+    ConfigurationWindow(wlcs::Client& client)
+        : client{client},
+          surface{client},
+          xdg_shell_surface{client, surface},
+          toplevel{xdg_shell_surface}
+    {
+        xdg_shell_surface.add_configure_notification([&](uint32_t serial)
+            {
+                xdg_surface_ack_configure(xdg_shell_surface, serial);
+                surface_configure_count++;
+            });
+
+        toplevel.add_configure_notification([this](int32_t width, int32_t height, struct wl_array *states)
+            {
+                state = wlcs::XdgToplevelStable::State{width, height, states};
+            });
+
+        wl_surface_commit(surface);
+        client.roundtrip();
+        surface.attach_buffer(window_width, window_height);
+        wl_surface_commit(surface);
+        dispatch_until_configure();
+    }
+
+    ~ConfigurationWindow()
+    {
+        client.roundtrip();
+    }
+
+    void dispatch_until_configure()
+    {
+        client.dispatch_until(
+            [prev_count = surface_configure_count, &current_count = surface_configure_count]()
+            {
+                return current_count > prev_count;
+            });
+    }
+
+    operator wlcs::Surface&() {return surface;}
+
+    operator wl_surface*() const {return surface;}
+    operator xdg_surface*() const {return xdg_shell_surface;}
+    operator xdg_toplevel*() const {return toplevel;}
+
+    wlcs::Client& client;
+    wlcs::Surface surface;
+    wlcs::XdgSurfaceStable xdg_shell_surface;
+    wlcs::XdgToplevelStable toplevel;
+
+    int surface_configure_count{0};
+    wlcs::XdgToplevelStable::State state{0, 0, nullptr};
+};
+}
+
+using XdgToplevelStableTest = wlcs::InProcessServer;
+
+// there *could* be a bug in these tests, but also the window manager may not be behaving properly
+// lets take another look when we've updated the window manager
+TEST_F(XdgToplevelStableTest, pointer_respects_window_geom_offset)
+{
+    const int offset_x = 35, offset_y = 12;
+    const int window_pos_x = 200, window_pos_y = 280;
+    const int pointer_x = window_pos_x + 20, pointer_y = window_pos_y + 30;
+
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    xdg_surface_set_window_geometry(window.xdg_shell_surface,
+                                    offset_x,
+                                    offset_y,
+                                    window.window_width - offset_x,
+                                    window.window_height - offset_y);
+    wl_surface_commit(window.surface);
+    the_server().move_surface_to(window.surface, window_pos_x, window_pos_y);
+
+    auto pointer = the_server().create_pointer();
+    pointer.move_to(pointer_x, pointer_y);
+    client.roundtrip();
+
+    ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)window.surface));
+    ASSERT_THAT(client.pointer_position(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - window_pos_x),
+                    wl_fixed_from_int(pointer_y - window_pos_y)))) << "set_window_geometry offset was ignored";
+    ASSERT_THAT(client.pointer_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - window_pos_x + offset_x),
+                    wl_fixed_from_int(pointer_y - window_pos_y + offset_y))));
+}
+
+TEST_F(XdgToplevelStableTest, touch_respects_window_geom_offset)
+{
+    const int offset_x = 35, offset_y = 12;
+    const int window_pos_x = 200, window_pos_y = 280;
+    const int pointer_x = window_pos_x + 20, pointer_y = window_pos_y + 30;
+
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    xdg_surface_set_window_geometry(window.xdg_shell_surface,
+                                        offset_x,
+                                        offset_y,
+                                        window.window_width - offset_x,
+                                        window.window_height - offset_y);
+    wl_surface_commit(window.surface);
+    the_server().move_surface_to(window.surface, window_pos_x, window_pos_y);
+
+    auto touch = the_server().create_touch();
+    touch.down_at(pointer_x, pointer_y);
+    client.roundtrip();
+
+    ASSERT_THAT(client.touched_window(), Eq((wl_surface*)window.surface));
+    ASSERT_THAT(client.touch_position(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - window_pos_x),
+                    wl_fixed_from_int(pointer_y - window_pos_y)))) << "set_window_geometry offset was ignored";
+    ASSERT_THAT(client.touch_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - window_pos_x + offset_x),
+                    wl_fixed_from_int(pointer_y - window_pos_y + offset_y))));
+}
+
+// TODO: set_window_geometry window size (something will need to be added to wlcs)
+
+TEST_F(XdgToplevelStableTest, surface_can_be_moved_interactively)
+{
+    int window_x = 100, window_y = 100;
+    int window_width = 420, window_height = 390;
+    int start_x = window_x + 5, start_y = window_y + 5;
+    int dx = 60, dy = -40;
+    int end_x = window_x + dx + 20, end_y = window_y + dy + 20;
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceStable xdg_shell_surface{client, surface};
+    wlcs::XdgToplevelStable toplevel{xdg_shell_surface};
+    surface.attach_buffer(window_width, window_height);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    the_server().move_surface_to(surface, window_x, window_y);
+
+    auto pointer = the_server().create_pointer();
+
+    bool button_down{false};
+    uint32_t last_serial{0};
+
+    client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+            last_serial = serial;
+            button_down = is_down;
+            return true;
+        });
+
+    pointer.move_to(start_x, start_y);
+    pointer.left_button_down();
+
+    client.dispatch_until([&](){
+            return button_down;
+        });
+
+    xdg_toplevel_move(toplevel, client.seat(), last_serial);
+    client.roundtrip();
+    pointer.move_to(start_x + dx, start_x + dy);
+    pointer.left_button_up();
+    client.roundtrip();
+
+    pointer.move_to(end_x, end_y);
+    client.roundtrip();
+
+    EXPECT_THAT(client.window_under_cursor(), Eq(static_cast<struct wl_surface*>(surface)));
+    EXPECT_THAT(client.pointer_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(end_x - window_x - dx),
+                    wl_fixed_from_int(end_y - window_y - dy))));
+
+    client.roundtrip();
+}
+
+TEST_F(XdgToplevelStableTest, pointer_leaves_surface_during_interactive_move)
+{
+    int window_x = 100, window_y = 100;
+    int window_width = 420, window_height = 390;
+    int start_x = window_x + 5, start_y = window_y + 5;
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceStable xdg_shell_surface{client, surface};
+    wlcs::XdgToplevelStable toplevel{xdg_shell_surface};
+    surface.attach_buffer(window_width, window_height);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    the_server().move_surface_to(surface, window_x, window_y);
+
+    auto pointer = the_server().create_pointer();
+
+    bool button_down{false};
+    uint32_t last_serial{0};
+
+    client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+            last_serial = serial;
+            button_down = is_down;
+            return true;
+        });
+
+    pointer.move_to(start_x, start_y);
+    pointer.left_button_down();
+
+    client.dispatch_until([&](){
+            return button_down;
+        });
+
+    xdg_toplevel_move(toplevel, client.seat(), last_serial);
+    client.dispatch_until([&](){
+            return !client.window_under_cursor();
+        });
+}
+
+TEST_F(XdgToplevelStableTest, surface_can_be_resized_interactively)
+{
+    int window_x = 100, window_y = 100;
+    int window_width = 420, window_height = 390;
+    int start_x = window_x + 5, start_y = window_y + 5;
+    int dx = 60, dy = -40;
+    int end_x = window_x + dx + 20, end_y = window_y + dy + 20;
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceStable xdg_shell_surface{client, surface};
+    wlcs::XdgToplevelStable toplevel{xdg_shell_surface};
+    surface.attach_buffer(window_width, window_height);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    the_server().move_surface_to(surface, window_x, window_y);
+
+    auto pointer = the_server().create_pointer();
+
+    bool button_down{false};
+    uint32_t last_serial{0};
+
+    client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+            last_serial = serial;
+            button_down = is_down;
+            return true;
+        });
+
+    pointer.move_to(start_x, start_y);
+    pointer.left_button_down();
+
+    client.dispatch_until([&](){
+            return button_down;
+        });
+
+    xdg_toplevel_resize(toplevel, client.seat(), last_serial, XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT);
+    client.roundtrip();
+    pointer.move_to(start_x + dx, start_x + dy);
+    pointer.left_button_up();
+    client.roundtrip();
+
+    pointer.move_to(end_x, end_y);
+    client.roundtrip();
+
+    EXPECT_THAT(client.window_under_cursor(), Eq(static_cast<struct wl_surface*>(surface)));
+    EXPECT_THAT(client.pointer_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(end_x - window_x - dx),
+                    wl_fixed_from_int(end_y - window_y - dy))));
+
+    client.roundtrip();
+}
+
+TEST_F(XdgToplevelStableTest, pointer_leaves_surface_during_interactive_resize)
+{
+    int window_x = 100, window_y = 100;
+    int window_width = 420, window_height = 390;
+    int start_x = window_x + 5, start_y = window_y + 5;
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceStable xdg_shell_surface{client, surface};
+    wlcs::XdgToplevelStable toplevel{xdg_shell_surface};
+    surface.attach_buffer(window_width, window_height);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    the_server().move_surface_to(surface, window_x, window_y);
+
+    auto pointer = the_server().create_pointer();
+
+    bool button_down{false};
+    uint32_t last_serial{0};
+
+    client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+            last_serial = serial;
+            button_down = is_down;
+            return true;
+        });
+
+    pointer.move_to(start_x, start_y);
+    pointer.left_button_down();
+
+    client.dispatch_until([&](){
+            return button_down;
+        });
+
+    xdg_toplevel_resize(toplevel, client.seat(), last_serial, XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT);
+    client.dispatch_until([&](){
+            return !client.window_under_cursor();
+        });
+}
+
+TEST_F(XdgToplevelStableTest, parent_can_be_set)
+{
+    const int window_pos_x = 200, window_pos_y = 280;
+
+    wlcs::Client client{the_server()};
+
+    ConfigurationWindow parent{client};
+    the_server().move_surface_to(parent.surface, window_pos_x, window_pos_y);
+
+    ConfigurationWindow child{client};
+    the_server().move_surface_to(child.surface, window_pos_x, window_pos_y);
+
+    xdg_toplevel_set_parent(child, parent);
+    wl_surface_commit(child.surface);
+    client.roundtrip();
+}
+
+TEST_F(XdgToplevelStableTest, null_parent_can_be_set)
+{
+    const int window_pos_x = 200, window_pos_y = 280;
+
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    the_server().move_surface_to(window.surface, window_pos_x, window_pos_y);
+
+    xdg_toplevel_set_parent(window, nullptr);
+    wl_surface_commit(window.surface);
+    client.roundtrip();
+}
+
+using XdgToplevelStableConfigurationTest = wlcs::InProcessServer;
+
+TEST_F(XdgToplevelStableConfigurationTest, defaults)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    // default values
+    EXPECT_THAT(state.width, Eq(0));
+    EXPECT_THAT(state.height, Eq(0));
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, window_can_maximize_itself)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    ASSERT_THAT(state.maximized, Eq(false)) << "test could not run as precondition failed";
+
+    xdg_toplevel_set_maximized(window);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.width, Gt(0));
+    EXPECT_THAT(state.height, Gt(0));
+    EXPECT_THAT(state.maximized, Eq(true));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, window_can_unmaximize_itself)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    xdg_toplevel_set_maximized(window);
+    window.dispatch_until_configure();
+
+    ASSERT_THAT(state.maximized, Eq(true)) << "test could not run as precondition failed";
+
+    xdg_toplevel_unset_maximized(window);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, window_can_fullscreen_itself)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    xdg_toplevel_set_fullscreen(window, nullptr);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.width, Gt(0));
+    EXPECT_THAT(state.height, Gt(0));
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(state.fullscreen, Eq(true));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, window_can_unfullscreen_itself)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    xdg_toplevel_set_fullscreen(window, nullptr);
+    window.dispatch_until_configure();
+
+    ASSERT_THAT(state.fullscreen, Eq(true)) << "test could not run as precondition failed";
+
+    xdg_toplevel_unset_fullscreen(window);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, DISABLED_window_stays_maximized_after_fullscreen)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    xdg_toplevel_set_maximized(window);
+    window.dispatch_until_configure();
+
+    ASSERT_THAT(state.maximized, Eq(true)) << "test could not run as precondition failed";
+
+    xdg_toplevel_set_fullscreen(window, nullptr);
+    window.dispatch_until_configure();
+
+    ASSERT_THAT(state.fullscreen, Eq(true)) << "test could not run as precondition failed";
+
+    xdg_toplevel_unset_fullscreen(window);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.width, Gt(0));
+    EXPECT_THAT(state.height, Gt(0));
+    EXPECT_THAT(state.maximized, Eq(true));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, DISABLED_window_can_maximize_itself_while_fullscreen)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    ASSERT_THAT(state.maximized, Eq(false)) << "test could not run as precondition failed";
+
+    xdg_toplevel_set_fullscreen(window, nullptr);
+    window.dispatch_until_configure();
+
+    ASSERT_THAT(state.fullscreen, Eq(true)) << "test could not run as precondition failed";
+
+    xdg_toplevel_set_maximized(window);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.maximized, Eq(true));
+
+    xdg_toplevel_unset_fullscreen(window);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.width, Gt(0));
+    EXPECT_THAT(state.height, Gt(0));
+    EXPECT_THAT(state.maximized, Eq(true));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelStableConfigurationTest, activated_state_follows_pointer)
+{
+    wlcs::Client client{the_server()};
+
+    ConfigurationWindow window_a{client};
+    auto& state_a = window_a.state;
+    int const a_x = 12, a_y = 15;
+    the_server().move_surface_to(window_a, a_x, a_y);
+
+    ConfigurationWindow window_b{client};
+    auto& state_b = window_b.state;
+    int const b_x = a_x + window_a.window_width + 27, b_y = 15;
+    the_server().move_surface_to(window_b, b_x, b_y);
+
+    auto pointer = the_server().create_pointer();
+
+    pointer.move_to(a_x + 10, a_y + 10);
+    pointer.left_click();
+    client.roundtrip();
+
+    ASSERT_THAT(state_a.activated, Eq(true));
+    ASSERT_THAT(state_b.activated, Eq(false));
+
+    pointer.move_to(b_x + 10, b_y + 10);
+    pointer.left_click();
+    client.roundtrip();
+
+    EXPECT_THAT(state_a.activated, Eq(false));
+    EXPECT_THAT(state_b.activated, Eq(true));
+}
diff --git a/tests/xdg_toplevel_v6.cpp b/tests/xdg_toplevel_v6.cpp
new file mode 100644 (file)
index 0000000..1e69a39
--- /dev/null
@@ -0,0 +1,514 @@
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2013 Collabora, Ltd.
+ * Copyright © 2018 Canonical Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "helpers.h"
+#include "in_process_server.h"
+#include "xdg_shell_v6.h"
+
+#include <gmock/gmock.h>
+
+#include <experimental/optional>
+
+using namespace testing;
+
+namespace
+{
+class ConfigurationWindow
+{
+public:
+    int const window_width = 200, window_height = 320;
+
+    ConfigurationWindow(wlcs::Client& client)
+        : client{client},
+          surface{client},
+          xdg_surface{client, surface},
+          toplevel{xdg_surface}
+    {
+        xdg_surface.add_configure_notification([&](uint32_t serial)
+            {
+                zxdg_surface_v6_ack_configure(xdg_surface, serial);
+                surface_configure_count++;
+            });
+
+        toplevel.add_configure_notification([this](int32_t width, int32_t height, struct wl_array *states)
+            {
+                state = wlcs::XdgToplevelV6::State{width, height, states};
+            });
+
+        wl_surface_commit(surface);
+        client.roundtrip();
+        surface.attach_buffer(window_width, window_height);
+        wl_surface_commit(surface);
+        dispatch_until_configure();
+    }
+
+    ~ConfigurationWindow()
+    {
+        client.roundtrip();
+    }
+
+    void dispatch_until_configure()
+    {
+        client.dispatch_until(
+            [prev_count = surface_configure_count, &current_count = surface_configure_count]()
+            {
+                return current_count > prev_count;
+            });
+    }
+
+    operator wlcs::Surface&() {return surface;}
+
+    operator wl_surface*() const {return surface;}
+    operator zxdg_surface_v6*() const {return xdg_surface;}
+    operator zxdg_toplevel_v6*() const {return toplevel;}
+
+    wlcs::Client& client;
+    wlcs::Surface surface;
+    wlcs::XdgSurfaceV6 xdg_surface;
+    wlcs::XdgToplevelV6 toplevel;
+
+    int surface_configure_count{0};
+    wlcs::XdgToplevelV6::State state{0, 0, nullptr};
+};
+}
+
+using XdgToplevelV6Test = wlcs::InProcessServer;
+
+// there *could* be a bug in these tests, but also the window manager may not be behaving properly
+// lets take another look when we've updated the window manager
+TEST_F(XdgToplevelV6Test, pointer_respects_window_geom_offset)
+{
+    const int offset_x = 35, offset_y = 12;
+    const int window_pos_x = 200, window_pos_y = 280;
+    const int pointer_x = window_pos_x + 20, pointer_y = window_pos_y + 30;
+
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    zxdg_surface_v6_set_window_geometry(window.xdg_surface,
+                                        offset_x,
+                                        offset_y,
+                                        window.window_width - offset_x,
+                                        window.window_height - offset_y);
+    wl_surface_commit(window.surface);
+    the_server().move_surface_to(window.surface, window_pos_x, window_pos_y);
+
+    auto pointer = the_server().create_pointer();
+    pointer.move_to(pointer_x, pointer_y);
+    client.roundtrip();
+
+    ASSERT_THAT(client.window_under_cursor(), Eq((wl_surface*)window.surface));
+    ASSERT_THAT(client.pointer_position(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - window_pos_x),
+                    wl_fixed_from_int(pointer_y - window_pos_y)))) << "set_window_geometry offset was ignored";
+    ASSERT_THAT(client.pointer_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - window_pos_x + offset_x),
+                    wl_fixed_from_int(pointer_y - window_pos_y + offset_y))));
+}
+
+TEST_F(XdgToplevelV6Test, touch_respects_window_geom_offset)
+{
+    const int offset_x = 35, offset_y = 12;
+    const int window_pos_x = 200, window_pos_y = 280;
+    const int pointer_x = window_pos_x + 20, pointer_y = window_pos_y + 30;
+
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    zxdg_surface_v6_set_window_geometry(window.xdg_surface,
+                                        offset_x,
+                                        offset_y,
+                                        window.window_width - offset_x,
+                                        window.window_height - offset_y);
+    wl_surface_commit(window.surface);
+    the_server().move_surface_to(window.surface, window_pos_x, window_pos_y);
+
+    auto touch = the_server().create_touch();
+    touch.down_at(pointer_x, pointer_y);
+    client.roundtrip();
+
+    ASSERT_THAT(client.touched_window(), Eq((wl_surface*)window.surface));
+    ASSERT_THAT(client.touch_position(),
+                Ne(std::make_pair(
+                    wl_fixed_from_int(pointer_x - window_pos_x),
+                    wl_fixed_from_int(pointer_y - window_pos_y)))) << "set_window_geometry offset was ignored";
+    ASSERT_THAT(client.touch_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(pointer_x - window_pos_x + offset_x),
+                    wl_fixed_from_int(pointer_y - window_pos_y + offset_y))));
+}
+
+// TODO: set_window_geometry window size (something will need to be added to wlcs)
+
+TEST_F(XdgToplevelV6Test, surface_can_be_moved_interactively)
+{
+    int window_x = 100, window_y = 100;
+    int window_width = 420, window_height = 390;
+    int start_x = window_x + 5, start_y = window_y + 5;
+    int dx = 60, dy = -40;
+    int end_x = window_x + dx + 20, end_y = window_y + dy + 20;
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceV6 xdg_surface{client, surface};
+    wlcs::XdgToplevelV6 toplevel{xdg_surface};
+    surface.attach_buffer(window_width, window_height);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    the_server().move_surface_to(surface, window_x, window_y);
+
+    auto pointer = the_server().create_pointer();
+
+    bool button_down{false};
+    uint32_t last_serial{0};
+
+    client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+            last_serial = serial;
+            button_down = is_down;
+            return true;
+        });
+
+    pointer.move_to(start_x, start_y);
+    pointer.left_button_down();
+
+    client.dispatch_until([&](){
+            return button_down;
+        });
+
+    zxdg_toplevel_v6_move(toplevel, client.seat(), last_serial);
+    client.roundtrip();
+    pointer.move_to(start_x + dx, start_x + dy);
+    pointer.left_button_up();
+    client.roundtrip();
+
+    client.dispatch_until([&](){
+            return !button_down;
+        });
+
+    pointer.move_to(end_x, end_y);
+    client.roundtrip();
+
+    EXPECT_THAT(client.window_under_cursor(), Eq(static_cast<struct wl_surface*>(surface)));
+    EXPECT_THAT(client.pointer_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(end_x - window_x - dx),
+                    wl_fixed_from_int(end_y - window_y - dy))));
+
+    client.roundtrip();
+}
+
+
+TEST_F(XdgToplevelV6Test, pointer_leaves_surface_during_interactive_move)
+{
+    int window_x = 100, window_y = 100;
+    int window_width = 420, window_height = 390;
+    int start_x = window_x + 5, start_y = window_y + 5;
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceV6 xdg_shell_surface{client, surface};
+    wlcs::XdgToplevelV6 toplevel{xdg_shell_surface};
+    surface.attach_buffer(window_width, window_height);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    the_server().move_surface_to(surface, window_x, window_y);
+
+    auto pointer = the_server().create_pointer();
+
+    bool button_down{false};
+    uint32_t last_serial{0};
+
+    client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+            last_serial = serial;
+            button_down = is_down;
+            return true;
+        });
+
+    pointer.move_to(start_x, start_y);
+    pointer.left_button_down();
+
+    client.dispatch_until([&](){
+            return button_down;
+        });
+
+    zxdg_toplevel_v6_move(toplevel, client.seat(), last_serial);
+    client.dispatch_until([&](){
+            return !client.window_under_cursor();
+        });
+}
+
+TEST_F(XdgToplevelV6Test, surface_can_be_resized_interactively)
+{
+    int window_x = 100, window_y = 100;
+    int window_width = 420, window_height = 390;
+    int start_x = window_x + 5, start_y = window_y + 5;
+    int dx = 60, dy = -40;
+    int end_x = window_x + dx + 20, end_y = window_y + dy + 20;
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceV6 xdg_shell_surface{client, surface};
+    wlcs::XdgToplevelV6 toplevel{xdg_shell_surface};
+    surface.attach_buffer(window_width, window_height);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    the_server().move_surface_to(surface, window_x, window_y);
+
+    auto pointer = the_server().create_pointer();
+
+    bool button_down{false};
+    uint32_t last_serial{0};
+
+    client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+            last_serial = serial;
+            button_down = is_down;
+            return true;
+        });
+
+    pointer.move_to(start_x, start_y);
+    pointer.left_button_down();
+
+    client.dispatch_until([&](){
+            return button_down;
+        });
+
+    zxdg_toplevel_v6_resize(toplevel, client.seat(), last_serial, ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT);
+    client.roundtrip();
+    pointer.move_to(start_x + dx, start_x + dy);
+    pointer.left_button_up();
+    client.roundtrip();
+
+    pointer.move_to(end_x, end_y);
+    client.roundtrip();
+
+    EXPECT_THAT(client.window_under_cursor(), Eq(static_cast<struct wl_surface*>(surface)));
+    EXPECT_THAT(client.pointer_position(),
+                Eq(std::make_pair(
+                    wl_fixed_from_int(end_x - window_x - dx),
+                    wl_fixed_from_int(end_y - window_y - dy))));
+
+    client.roundtrip();
+}
+
+TEST_F(XdgToplevelV6Test, pointer_leaves_surface_during_interactive_resize)
+{
+    int window_x = 100, window_y = 100;
+    int window_width = 420, window_height = 390;
+    int start_x = window_x + 5, start_y = window_y + 5;
+
+    wlcs::Client client{the_server()};
+    wlcs::Surface surface{client};
+    wlcs::XdgSurfaceV6 xdg_shell_surface{client, surface};
+    wlcs::XdgToplevelV6 toplevel{xdg_shell_surface};
+    surface.attach_buffer(window_width, window_height);
+    wl_surface_commit(surface);
+    client.roundtrip();
+
+    the_server().move_surface_to(surface, window_x, window_y);
+
+    auto pointer = the_server().create_pointer();
+
+    bool button_down{false};
+    uint32_t last_serial{0};
+
+    client.add_pointer_button_notification([&](uint32_t serial, uint32_t, bool is_down) -> bool {
+            last_serial = serial;
+            button_down = is_down;
+            return true;
+        });
+
+    pointer.move_to(start_x, start_y);
+    pointer.left_button_down();
+
+    client.dispatch_until([&](){
+            return button_down;
+        });
+
+    zxdg_toplevel_v6_resize(toplevel, client.seat(), last_serial, ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT);
+    client.dispatch_until([&](){
+            return !client.window_under_cursor();
+        });
+}
+
+TEST_F(XdgToplevelV6Test, parent_can_be_set)
+{
+    const int window_pos_x = 200, window_pos_y = 280;
+
+    wlcs::Client client{the_server()};
+
+    ConfigurationWindow parent{client};
+    the_server().move_surface_to(parent.surface, window_pos_x, window_pos_y);
+
+    ConfigurationWindow child{client};
+    the_server().move_surface_to(child.surface, window_pos_x, window_pos_y);
+
+    zxdg_toplevel_v6_set_parent(child, parent);
+    wl_surface_commit(child.surface);
+    client.roundtrip();
+}
+
+TEST_F(XdgToplevelV6Test, null_parent_can_be_set)
+{
+    const int window_pos_x = 200, window_pos_y = 280;
+
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    the_server().move_surface_to(window.surface, window_pos_x, window_pos_y);
+
+    zxdg_toplevel_v6_set_parent(window, nullptr);
+    wl_surface_commit(window.surface);
+    client.roundtrip();
+}
+
+// TODO: interactive resize
+// This would probably make sense as a parameterized test, with resizing in all directions
+// Like move, resize is not implemented in the current WLCS window manager, and should not be tested until it is
+
+using XdgToplevelV6ConfigurationTest = wlcs::InProcessServer;
+
+TEST_F(XdgToplevelV6ConfigurationTest, defaults)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    // default values
+    EXPECT_THAT(state.width, Eq(0));
+    EXPECT_THAT(state.height, Eq(0));
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, window_can_maximize_itself)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    zxdg_toplevel_v6_set_maximized(window);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.width, Gt(0));
+    EXPECT_THAT(state.height, Gt(0));
+    EXPECT_THAT(state.maximized, Eq(true));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, window_can_unmaximize_itself)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    zxdg_toplevel_v6_set_maximized(window);
+    window.dispatch_until_configure();
+
+    ASSERT_THAT(state.maximized, Eq(true)) << "test could not run as precondition failed";
+
+    zxdg_toplevel_v6_unset_maximized(window);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, window_can_fullscreen_itself)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    zxdg_toplevel_v6_set_fullscreen(window, nullptr);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.width, Gt(0));
+    EXPECT_THAT(state.height, Gt(0));
+    EXPECT_THAT(state.maximized, Eq(false)); // is this right? should it not be maximized, even when fullscreen?
+    EXPECT_THAT(state.fullscreen, Eq(true));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, window_can_unfullscreen_itself)
+{
+    wlcs::Client client{the_server()};
+    ConfigurationWindow window{client};
+    auto& state = window.state;
+
+    zxdg_toplevel_v6_set_fullscreen(window, nullptr);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.fullscreen, Eq(true)) << "test could not run as precondition failed";
+
+    zxdg_toplevel_v6_unset_fullscreen(window);
+    window.dispatch_until_configure();
+
+    EXPECT_THAT(state.maximized, Eq(false));
+    EXPECT_THAT(state.fullscreen, Eq(false));
+    EXPECT_THAT(state.resizing, Eq(false));
+    EXPECT_THAT(state.activated, Eq(true));
+}
+
+TEST_F(XdgToplevelV6ConfigurationTest, activated_state_follows_pointer)
+{
+    wlcs::Client client{the_server()};
+
+    ConfigurationWindow window_a{client};
+    auto& state_a = window_a.state;
+    int const a_x = 12, a_y = 15;
+    the_server().move_surface_to(window_a, a_x, a_y);
+
+    ConfigurationWindow window_b{client};
+    auto& state_b = window_b.state;
+    int const b_x = a_x + window_a.window_width + 27, b_y = 15;
+    the_server().move_surface_to(window_b, b_x, b_y);
+
+    auto pointer = the_server().create_pointer();
+
+    pointer.move_to(a_x + 10, a_y + 10);
+    pointer.left_click();
+    client.roundtrip();
+
+    ASSERT_THAT(state_a.activated, Eq(true));
+    ASSERT_THAT(state_b.activated, Eq(false));
+
+    pointer.move_to(b_x + 10, b_y + 10);
+    pointer.left_click();
+    client.roundtrip();
+
+    EXPECT_THAT(state_a.activated, Eq(false));
+    EXPECT_THAT(state_b.activated, Eq(true));
+}
diff --git a/tools/make_release_tarball b/tools/make_release_tarball
new file mode 100755 (executable)
index 0000000..92c0c4e
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+set -ex
+
+RELEASE_TAG=HEAD
+
+while [ $# -gt 0 ]
+do
+  if [ "$1" == "--skip-checks" ]
+  then
+    skip_checks=true
+  fi
+  if [ "$1" == "--release-tag" ]
+  then
+    shift
+    RELEASE_TAG=$1
+  fi
+  shift
+done
+
+if [ ! -e tools/make_release_tarball  -o  "$1" != "" ]
+then
+  echo "Handy script for creating tarball: Use from the checkout directory"
+  exit 0
+fi
+
+set -e
+
+VERSION=$(grep -E "project\(wlcs VERSION [[:digit:]]+(\.[[:digit:]]+)+\)" CMakeLists.txt | grep -E --only-matching "[[:digit:]]+(.[[:digit:]])+")
+
+VERSIONED_NAME=wlcs-$VERSION
+
+SCRATCH_DIR=$(mktemp -d)
+BUILD_DIR=$(mktemp -d)
+INSTALL_DIR=$(mktemp -d)
+
+function cleanup() {
+       ARG=$?
+       [ -d $SCRATCH_DIR ] && rm -r $SCRATCH_DIR
+       [ -d $BUILD_DIR ] && rm -r $BUILD_DIR
+       [ -d $INSTALL_DIR ] && rm -r $INSTALL_DIR
+       exit $ARG
+}
+
+trap cleanup EXIT
+
+echo "Generating WLCS tarball…"
+git archive --format=tar --prefix=$VERSIONED_NAME/ $RELEASE_TAG | xz -9 > $SCRATCH_DIR/$VERSIONED_NAME.tar.xz
+
+if [ ! -v skip_checks ]
+then
+  (cd $SCRATCH_DIR; tar xvJf $SCRATCH_DIR/$VERSIONED_NAME.tar.xz)
+
+  echo "Testing that the tarball is buildable"
+  (cd $BUILD_DIR ; cmake $SCRATCH_DIR/$VERSIONED_NAME )
+  make -C $BUILD_DIR -j $(nproc)
+
+  echo "Testing that the tarball is installable"
+  make -C $BUILD_DIR install DESTDIR=$INSTALL_DIR
+fi
+
+mv $SCRATCH_DIR/$VERSIONED_NAME.tar.xz .
+echo "$VERSIONED_NAME.tar.xz successfully created and tested"
+
diff --git a/wlcs.pc.in b/wlcs.pc.in
new file mode 100644 (file)
index 0000000..baaebe1
--- /dev/null
@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}/@CMAKE_INSTALL_BINDIR@
+libexecdir=${prefix}/@CMAKE_INSTALL_LIBEXECDIR@
+includedir=${prefix}/include
+
+test_runner=${libexecdir}/wlcs/wlcs
+
+Name: wlcs
+Description: Wayland Conformance Suite test harness
+Version: @PROJECT_VERSION@
+Requires.private: wayland-client
+Cflags: -I${includedir}