From 2115f6526e28487d4b0fa6e03df95155a38f44e4 Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Fri, 26 Feb 2021 21:03:29 +0000 Subject: [PATCH 1/1] Import wlcs_1.2.1.orig.tar.xz [dgit import orig wlcs_1.2.1.orig.tar.xz] --- .gitignore | 3 + .travis.yml | 82 + CMakeLists.txt | 277 ++ COPYING.GPL2 | 339 ++ COPYING.GPL3 | 676 ++++ README.rst | 52 + bors.toml | 4 + cmake/FindGtestGmock.cmake | 69 + debian/changelog | 38 + debian/compat | 1 + debian/control | 29 + debian/copyright | 73 + debian/rules | 29 + debian/source/format | 1 + debian/upstream/signing-key.asc | 366 +++ debian/watch | 6 + example/mir_integration.cpp | 909 ++++++ include/active_listeners.h | 54 + include/data_device.h | 195 ++ include/gtest_helpers.h | 46 + include/gtk_primary_selection.h | 162 + include/helpers.h | 44 + include/in_process_server.h | 374 +++ include/input_method.h | 83 + include/layer_shell_v1.h | 71 + include/mutex.h | 132 + include/pointer_constraints_unstable_v1.h | 104 + include/primary_selection.h | 162 + include/relative_pointer_unstable_v1.h | 75 + include/shared_library.h | 55 + include/surface_builder.h | 102 + include/version_specifier.h | 62 + include/wl_handle.h | 103 + include/wl_interface_descriptor.h | 61 + include/wlcs/display_server.h | 198 ++ include/wlcs/pointer.h | 72 + include/wlcs/touch.h | 48 + include/xdg_output_v1.h | 75 + include/xdg_shell_stable.h | 179 ++ include/xdg_shell_v6.h | 173 ++ spread.yaml | 29 + spread/build/alpine/task.yaml | 22 + spread/build/fedora/task.yaml | 23 + spread/build/ubuntu/task.yaml | 34 + src/data_device.cpp | 144 + src/gtk_primary_selection.cpp | 90 + src/helpers.cpp | 121 + src/in_process_server.cpp | 1940 ++++++++++++ src/input_method.cpp | 131 + src/layer_shell_v1.cpp | 68 + src/main.cpp | 100 + src/pointer_constraints_unstable_v1.cpp | 93 + src/primary_selection.cpp | 90 + src/protocol/gtk-primary-selection.xml | 225 ++ .../pointer-constraints-unstable-v1.xml | 339 ++ .../primary-selection-unstable-v1.xml | 225 ++ src/protocol/relative-pointer-unstable-v1.xml | 136 + src/protocol/wayland.xml | 2746 +++++++++++++++++ ...oreign-toplevel-management-unstable-v1.xml | 259 ++ src/protocol/wlr-layer-shell-unstable-v1.xml | 285 ++ src/protocol/xdg-output-unstable-v1.xml | 220 ++ src/protocol/xdg-shell-unstable-v6.xml | 1044 +++++++ src/protocol/xdg-shell.xml | 1120 +++++++ src/relative_pointer_unstable_v1.cpp | 58 + src/shared_library.cpp | 55 + src/surface_builder.cpp | 145 + src/termcolor.hpp | 557 ++++ src/test_c_compile.c | 7 + src/thread_proxy.h | 465 +++ src/version_specifier.cpp | 61 + src/xdg_output_v1.cpp | 144 + src/xdg_shell_stable.cpp | 112 + src/xdg_shell_v6.cpp | 106 + src/xfail_supporting_test_listener.cpp | 212 ++ src/xfail_supporting_test_listener.h | 78 + tests/copy_cut_paste.cpp | 129 + tests/frame_submission.cpp | 81 + tests/gtk_primary_selection.cpp | 231 ++ tests/pointer_constraints.cpp | 119 + tests/primary_selection.cpp | 231 ++ tests/relative_pointer.cpp | 77 + tests/self_test.cpp | 187 ++ tests/subsurfaces.cpp | 936 ++++++ tests/surface_input_regions.cpp | 837 +++++ tests/test_bad_buffer.cpp | 191 ++ tests/test_surface_events.cpp | 638 ++++ tests/touches.cpp | 169 + tests/wl_output.cpp | 57 + tests/wlr_foreign_toplevel_management_v1.cpp | 918 ++++++ tests/wlr_layer_shell_v1.cpp | 751 +++++ tests/xdg_output_v1.cpp | 43 + tests/xdg_popup.cpp | 757 +++++ tests/xdg_surface_stable.cpp | 179 ++ tests/xdg_surface_v6.cpp | 64 + tests/xdg_toplevel_stable.cpp | 563 ++++ tests/xdg_toplevel_v6.cpp | 514 +++ tools/make_release_tarball | 64 + wlcs.pc.in | 12 + 98 files changed, 24816 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CMakeLists.txt create mode 100644 COPYING.GPL2 create mode 100644 COPYING.GPL3 create mode 100644 README.rst create mode 100644 bors.toml create mode 100644 cmake/FindGtestGmock.cmake create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 debian/upstream/signing-key.asc create mode 100644 debian/watch create mode 100644 example/mir_integration.cpp create mode 100644 include/active_listeners.h create mode 100644 include/data_device.h create mode 100644 include/gtest_helpers.h create mode 100644 include/gtk_primary_selection.h create mode 100644 include/helpers.h create mode 100644 include/in_process_server.h create mode 100644 include/input_method.h create mode 100644 include/layer_shell_v1.h create mode 100644 include/mutex.h create mode 100644 include/pointer_constraints_unstable_v1.h create mode 100644 include/primary_selection.h create mode 100644 include/relative_pointer_unstable_v1.h create mode 100644 include/shared_library.h create mode 100644 include/surface_builder.h create mode 100644 include/version_specifier.h create mode 100644 include/wl_handle.h create mode 100644 include/wl_interface_descriptor.h create mode 100644 include/wlcs/display_server.h create mode 100644 include/wlcs/pointer.h create mode 100644 include/wlcs/touch.h create mode 100644 include/xdg_output_v1.h create mode 100644 include/xdg_shell_stable.h create mode 100644 include/xdg_shell_v6.h create mode 100644 spread.yaml create mode 100644 spread/build/alpine/task.yaml create mode 100644 spread/build/fedora/task.yaml create mode 100644 spread/build/ubuntu/task.yaml create mode 100644 src/data_device.cpp create mode 100644 src/gtk_primary_selection.cpp create mode 100644 src/helpers.cpp create mode 100644 src/in_process_server.cpp create mode 100644 src/input_method.cpp create mode 100644 src/layer_shell_v1.cpp create mode 100644 src/main.cpp create mode 100644 src/pointer_constraints_unstable_v1.cpp create mode 100644 src/primary_selection.cpp create mode 100644 src/protocol/gtk-primary-selection.xml create mode 100644 src/protocol/pointer-constraints-unstable-v1.xml create mode 100644 src/protocol/primary-selection-unstable-v1.xml create mode 100644 src/protocol/relative-pointer-unstable-v1.xml create mode 100644 src/protocol/wayland.xml create mode 100644 src/protocol/wlr-foreign-toplevel-management-unstable-v1.xml create mode 100644 src/protocol/wlr-layer-shell-unstable-v1.xml create mode 100644 src/protocol/xdg-output-unstable-v1.xml create mode 100644 src/protocol/xdg-shell-unstable-v6.xml create mode 100644 src/protocol/xdg-shell.xml create mode 100644 src/relative_pointer_unstable_v1.cpp create mode 100644 src/shared_library.cpp create mode 100644 src/surface_builder.cpp create mode 100644 src/termcolor.hpp create mode 100644 src/test_c_compile.c create mode 100644 src/thread_proxy.h create mode 100644 src/version_specifier.cpp create mode 100644 src/xdg_output_v1.cpp create mode 100644 src/xdg_shell_stable.cpp create mode 100644 src/xdg_shell_v6.cpp create mode 100644 src/xfail_supporting_test_listener.cpp create mode 100644 src/xfail_supporting_test_listener.h create mode 100644 tests/copy_cut_paste.cpp create mode 100644 tests/frame_submission.cpp create mode 100644 tests/gtk_primary_selection.cpp create mode 100644 tests/pointer_constraints.cpp create mode 100644 tests/primary_selection.cpp create mode 100644 tests/relative_pointer.cpp create mode 100644 tests/self_test.cpp create mode 100644 tests/subsurfaces.cpp create mode 100644 tests/surface_input_regions.cpp create mode 100644 tests/test_bad_buffer.cpp create mode 100644 tests/test_surface_events.cpp create mode 100644 tests/touches.cpp create mode 100644 tests/wl_output.cpp create mode 100644 tests/wlr_foreign_toplevel_management_v1.cpp create mode 100644 tests/wlr_layer_shell_v1.cpp create mode 100644 tests/xdg_output_v1.cpp create mode 100644 tests/xdg_popup.cpp create mode 100644 tests/xdg_surface_stable.cpp create mode 100644 tests/xdg_surface_v6.cpp create mode 100644 tests/xdg_toplevel_stable.cpp create mode 100644 tests/xdg_toplevel_v6.cpp create mode 100755 tools/make_release_tarball create mode 100644 wlcs.pc.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05cadb2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +/build/ +/cmake-build-debug/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6253e79 --- /dev/null +++ b/.travis.yml @@ -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 index 0000000..de550f5 --- /dev/null +++ b/CMakeLists.txt @@ -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 . +# +# Authored by: Thomas Voss , +# Alan Griffiths , +# Christopher James Halse Rogers + +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 index 0000000..d159169 --- /dev/null +++ b/COPYING.GPL2 @@ -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. + + + Copyright (C) + + 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. + + , 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 index 0000000..4432540 --- /dev/null +++ b/COPYING.GPL3 @@ -0,0 +1,676 @@ + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. + diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..50f8ec1 --- /dev/null +++ b/README.rst @@ -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 `_ 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 `_, +and the `Weston test suite `_ +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 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 index 0000000..230d988 --- /dev/null +++ b/cmake/FindGtestGmock.cmake @@ -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 index 0000000..a4be7f1 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,38 @@ +wlcs (1.2.1-0ubuntu0) groovy; urgency=medium + + * New upstream release. Notable changes: + + Fix cut & paste test + + -- Alan Griffiths 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 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 Tue, 23 Jul 2019 10:37:52 +1000 + +wlcs (1.0-0ubuntu0) disco; urgency=medium + + * Initial release + + -- Christopher James Halse Rogers Tue, 08 Jan 2019 11:32:06 +1100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..e65efd8 --- /dev/null +++ b/debian/control @@ -0,0 +1,29 @@ +Source: wlcs +Priority: optional +Maintainer: Christopher James Halse Rogers +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 index 0000000..fa4f094 --- /dev/null +++ b/debian/copyright @@ -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 +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 + . + 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 index 0000000..2791285 --- /dev/null +++ b/debian/rules @@ -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 index 0000000..d3827e7 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +1.0 diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc new file mode 100644 index 0000000..f538fd7 --- /dev/null +++ b/debian/upstream/signing-key.asc @@ -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 index 0000000..4c1712d --- /dev/null +++ b/debian/watch @@ -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 index 0000000..5af386d --- /dev/null +++ b/example/mir_integration.cpp @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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{}(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 const&) override + { + } + + void stopping(std::shared_ptr const&) override + { + } + + void focused(std::shared_ptr const&) override + { + } + + void unfocused() override + { + } + + void surface_created( + mir::scene::Session&, + std::shared_ptr 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 const&) override + { + // TODO: Maybe delete from map? + } + + void buffer_stream_created( + mir::scene::Session&, + std::shared_ptr 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 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 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(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> surface_map; + std::unordered_map, wl_resource*> stream_map; + + std::experimental::optional latest_client; + std::unordered_map client_session_map; + std::unordered_map resource_listener; + }; + wlcs::WaitableMutex state; + + struct Listeners + { + Listeners(wlcs::WaitableMutex* const state) + : state{state} + { + } + + wl_listener client_listener; + + wl_resource* last_wl_surface{nullptr}; + wl_resource* last_wl_window{nullptr}; + + wlcs::WaitableMutex* const state; + } listeners; + + static void resource_created(wl_listener* listener, void* ctx) + { + auto resource = static_cast(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(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&& work) override + { + { + std::lock_guard 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 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{new WaylandExecutor{loop}}; + auto shim = std::make_unique(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 get_work() + { + std::lock_guard 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(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 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 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> workqueue; + + wl_event_source* const notify_source; + + struct DestructionShim + { + explicit DestructionShim(std::shared_ptr const& executor) + : executor{executor} + { + } + + std::shared_ptr const executor; + wl_listener destruction_listener; + }; + static_assert( + std::is_standard_layout::value, + "DestructionShim must be Standard Layout for wl_container_of to be defined behaviour"); +}; +} + +class InputEventListener; + +struct MirWlcsDisplayServer : mtf::AsyncServerRunner +{ + testing::NiceMock mockgl; + std::shared_ptr const resource_mapper{std::make_shared()}; + std::shared_ptr event_listener; + std::shared_ptr executor; + std::atomic 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 expect_event_with_time( + std::chrono::nanoseconds event_time) + { + auto done_signal = std::make_shared(); + 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 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 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>> expected_events; + MirWlcsDisplayServer& runner; +}; + +template +void emit_mir_event(MirWlcsDisplayServer* runner, + mir::UniqueModulePtr& emitter, + T event) +{ + auto event_time = std::chrono::duration_cast( + 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(server); + + runner->start_server(); + + runner->started.wait_for(a_long_time); +} + +void wlcs_server_stop(WlcsDisplayServer* server) +{ + auto runner = reinterpret_cast(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(); + }); + + runner->server.override_the_session_listener( + [runner]() + { + return runner->resource_mapper; + }); + + runner->event_listener = std::make_shared(*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 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 const wrapped; + }; + return std::make_shared(runner, wrappee); + }); + + return reinterpret_cast(runner); +} + +void wlcs_destroy_server(WlcsDisplayServer* server) +{ + auto runner = reinterpret_cast(server); + delete runner; +} + +int wlcs_server_create_client_socket(WlcsDisplayServer* server) +{ + auto runner = reinterpret_cast(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(server); + + auto constexpr uid = "mouse-uid"; + + class DeviceObserver : public mir::input::InputDeviceObserver + { + public: + DeviceObserver(std::shared_ptr const& done) + : done{done} + { + } + + void device_added(std::shared_ptr const& device) override + { + if (device->unique_id() == uid) + seen_device = true; + } + + void device_changed(std::shared_ptr const&) override + { + } + + void device_removed(std::shared_ptr const&) override + { + } + + void changes_complete() override + { + if (seen_device) + done->raise(); + } + + private: + std::shared_ptr const done; + bool seen_device{false}; + }; + + auto mouse_added = std::make_shared(); + auto observer = std::make_shared(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(fake_pointer); +} + +void wlcs_destroy_pointer(WlcsPointer* pointer) +{ + delete reinterpret_cast(pointer); +} + +void wlcs_pointer_move_relative(WlcsPointer* pointer, wl_fixed_t x, wl_fixed_t y) +{ + auto device = reinterpret_cast(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(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(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(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(server); + + auto constexpr uid = "touch-uid"; + + class DeviceObserver : public mir::input::InputDeviceObserver + { + public: + DeviceObserver(std::shared_ptr const& done) + : done{done} + { + } + + void device_added(std::shared_ptr const& device) override + { + if (device->unique_id() == uid) + seen_device = true; + } + + void device_changed(std::shared_ptr const&) override + { + } + + void device_removed(std::shared_ptr const&) override + { + } + + void changes_complete() override + { + if (seen_device) + done->raise(); + } + + private: + std::shared_ptr const done; + bool seen_device{false}; + }; + + auto touch_added = std::make_shared(); + auto observer = std::make_shared(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(fake_touch); +} + +void wlcs_destroy_touch(WlcsTouch* touch) +{ + delete reinterpret_cast(touch); +} + +void wlcs_touch_down(WlcsTouch* touch, int x, int y) +{ + auto device = reinterpret_cast(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(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(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(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(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 index 0000000..8f0de8b --- /dev/null +++ b/include/active_listeners.h @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#ifndef WLCS_ACTIVE_LISTENERS_H +#define WLCS_ACTIVE_LISTENERS_H + +#include +#include + +namespace wlcs +{ +class ActiveListeners +{ +public: + void add(void* listener) + { + std::lock_guard lock{mutex}; + listeners.insert(listener); + } + + void del(void* listener) + { + std::lock_guard lock{mutex}; + listeners.erase(listener); + } + + bool includes(void* listener) const + { + std::lock_guard lock{mutex}; + return listeners.find(listener) != end(listeners); + } + +private: + std::mutex mutable mutex; + std::set listeners; +}; +} + +#endif //WLCS_ACTIVE_LISTENERS_H diff --git a/include/data_device.h b/include/data_device.h new file mode 100644 index 0000000..35adce5 --- /dev/null +++ b/include/data_device.h @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#ifndef WLCS_DATA_DEVICE_H +#define WLCS_DATA_DEVICE_H + +#include "active_listeners.h" +#include "wl_interface_descriptor.h" +#include "wl_handle.h" + +#include + +#include + +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 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 self; +}; +} + +#endif //WLCS_DATA_DEVICE_H diff --git a/include/gtest_helpers.h b/include/gtest_helpers.h new file mode 100644 index 0000000..2ad1b92 --- /dev/null +++ b/include/gtest_helpers.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#include +#include +#include + +namespace std +{ +namespace chrono +{ +/// GTest helper to pretty-print time-points +template +void PrintTo(time_point const& time, ostream* os) +{ + auto remainder = time.time_since_epoch(); + auto const hours = duration_cast(remainder); + remainder -= hours; + auto const minutes = duration_cast(remainder); + remainder -= minutes; + auto const seconds = duration_cast(remainder); + remainder -= seconds; + auto const nsec = duration_cast(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 index 0000000..4028dfe --- /dev/null +++ b/include/gtk_primary_selection.h @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#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 +#include +#include + +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 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 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 index 0000000..3a22001 --- /dev/null +++ b/include/helpers.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#ifndef WLCS_HELPERS_H_ +#define WLCS_HELPERS_H_ + +#include +#include + +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 const& entry_point); + +std::shared_ptr 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 index 0000000..5178ec1 --- /dev/null +++ b/include/in_process_server.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#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 + +#include +#include +#include +#include +#include + +#include "shared_library.h" +#include "wl_handle.h" + +#include + +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 +{ + 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 + Pointer( + WlcsPointer* raw_device, + std::shared_ptr const& proxy, + std::shared_ptr const& keep_dso_loaded); + + class Impl; + std::unique_ptr 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 + Touch( + WlcsTouch* raw_device, + std::shared_ptr const& proxy, + std::shared_ptr const& keep_dso_loaded); + + class Impl; + std::unique_ptr impl; +}; + +class Surface; + +class Server +{ +public: + Server( + std::shared_ptr 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> supported_extensions(); +private: + class Impl; + std::unique_ptr 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 const& on_frame); + void attach_visible_buffer(int width, int height); + void run_on_destruction(std::function callback); + + bool has_focus() const; + std::pair pointer_position() const; + + Client& owner() const; + +private: + class Impl; + std::unique_ptr 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; +}; + +class ShmBuffer +{ +public: + ShmBuffer(Client& client, int width, int height); + ~ShmBuffer(); + + ShmBuffer(ShmBuffer&& other); + + operator wl_buffer*() const; + + void add_release_listener(std::function const &on_release); + +private: + class Impl; + std::unique_ptr impl; +}; + +struct OutputState +{ + OutputState(wl_output* output) + : output{output} + { + } + + wl_output* output; + std::experimental::optional> geometry_position; + std::experimental::optional> mode_size; + std::experimental::optional 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 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 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 pointer_position() const; + std::pair touch_position() const; + + using PointerEnterNotifier = + std::function; + using PointerLeaveNotifier = + std::function; + using PointerMotionNotifier = + std::function; + using PointerButtonNotifier = + std::function; + 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 const& predicate, + std::chrono::seconds timeout = std::chrono::seconds{10}); + + template + auto bind_if_supported(VersionSpecifier const& version) -> WlHandle + { + return wrap_wl_object( + static_cast(bind_if_supported(*WlInterfaceDescriptor::interface, version))); + } + + auto bind_if_supported(wl_interface const& interface, VersionSpecifier const& version) const -> void*; + + void roundtrip(); + +private: + class Impl; + std::unique_ptr 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 index 0000000..0a16f89 --- /dev/null +++ b/include/input_method.h @@ -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 . + * + * Authored by: William Wold + */ + +#ifndef WLCS_INPUT_METHOD_H_ +#define WLCS_INPUT_METHOD_H_ + +#include +#include +#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 position) = 0; + /// Can only be called while device is down + virtual void move_to(std::pair position) = 0; + /// Can only be called while device is down + virtual void up() = 0; + }; + + virtual auto create_device(wlcs::Server& server) -> std::unique_ptr = 0; + virtual auto current_surface(wlcs::Client const& client) -> wl_surface* = 0; + virtual auto position_on_surface(wlcs::Client const& client) -> std::pair = 0; + + static auto all_input_methods() -> std::vector>; + + std::string const name; +}; + +struct PointerInputMethod : InputMethod +{ + PointerInputMethod() : InputMethod{"pointer"} {} + + struct Pointer; + + auto create_device(wlcs::Server& server) -> std::unique_ptr override; + auto current_surface(wlcs::Client const& client) -> wl_surface* override; + auto position_on_surface(wlcs::Client const& client) -> std::pair override; +}; + +struct TouchInputMethod : InputMethod +{ + TouchInputMethod() : InputMethod{"touch"} {} + + struct Touch; + + auto create_device(wlcs::Server& server) -> std::unique_ptr override; + auto current_surface(wlcs::Client const& client) -> wl_surface* override; + auto position_on_surface(wlcs::Client const& client) -> std::pair override; +}; + +} + +namespace std +{ +std::ostream& operator<<(std::ostream& out, std::shared_ptr 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 index 0000000..2e0ee49 --- /dev/null +++ b/include/layer_shell_v1.h @@ -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 . + * + * Authored by: William Wold + */ + +#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 layer_shell; + WlHandle 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 index 0000000..76aeda9 --- /dev/null +++ b/include/mutex.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#ifndef WLCS_MUTEX_H_ +#define WLCS_MUTEX_H_ + +#include +#include +#include + +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 +class MutexGuard +{ +public: + MutexGuard(std::unique_lock&& 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 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 +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 lock() + { + return MutexGuard{std::unique_lock{mutex}, value}; + } + +protected: + std::mutex mutex; + Guarded value; +}; + +template +class WaitableMutex : public Mutex +{ +public: + using Mutex::Mutex; + + template + MutexGuard wait_for(Predicate predicate, std::chrono::duration timeout) + { + std::unique_lock 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{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 index 0000000..b84bb02 --- /dev/null +++ b/include/pointer_constraints_unstable_v1.h @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#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 + +#include + +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 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 index 0000000..91798af --- /dev/null +++ b/include/primary_selection.h @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#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 +#include +#include + +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 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 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 index 0000000..7b68074 --- /dev/null +++ b/include/relative_pointer_unstable_v1.h @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#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 + +#include + +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 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 index 0000000..ef275f7 --- /dev/null +++ b/include/shared_library.h @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#ifndef MIR_SHARED_LIBRARY_H_ +#define MIR_SHARED_LIBRARY_H_ + +#include + +namespace wlcs +{ +class SharedLibrary +{ +public: + explicit SharedLibrary(char const* library_name); + explicit SharedLibrary(std::string const& library_name); + ~SharedLibrary() noexcept; + + template + FunctionPtr load_function(char const* function_name) const + { + FunctionPtr result{}; + (void*&)result = load_symbol(function_name); + return result; + } + + template + FunctionPtr load_function(std::string const& function_name) const + { + return load_function(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 index 0000000..fd8a42c --- /dev/null +++ b/include/surface_builder.h @@ -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 . + * + * Authored by: William Wold + */ + +#ifndef WLCS_SURFACE_BUILDER_H_ +#define WLCS_SURFACE_BUILDER_H_ + +#include +#include +#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 position, + std::pair size) const -> std::unique_ptr = 0; + + std::string const name; + + static auto all_surface_types() -> std::vector>; + static auto toplevel_surface_types() -> std::vector>; +}; + +struct WlShellSurfaceBuilder : SurfaceBuilder +{ + WlShellSurfaceBuilder() : SurfaceBuilder{"wl_shell_surface"} {} + + auto build( + wlcs::Server& server, + wlcs::Client& client, + std::pair position, + std::pair size) const -> std::unique_ptr override; +}; + +struct XdgV6SurfaceBuilder : SurfaceBuilder +{ + XdgV6SurfaceBuilder() : SurfaceBuilder{"zxdg_surface_v6"} {} + + auto build( + wlcs::Server& server, + wlcs::Client& client, + std::pair position, + std::pair size) const -> std::unique_ptr 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 position, + std::pair size) const -> std::unique_ptr override; + + int left_offset, top_offset, right_offset, bottom_offset; +}; + +struct SubsurfaceBuilder : SurfaceBuilder +{ + SubsurfaceBuilder(std::pair offset); + + auto build( + wlcs::Server& server, + wlcs::Client& client, + std::pair position, + std::pair size) const -> std::unique_ptr override; + + std::pair offset; +}; + +// TODO: popup surfaces +} + +namespace std +{ +std::ostream& operator<<(std::ostream& out, std::shared_ptr const& param); +} + +#endif // WLCS_SURFACE_BUILDER_H_ diff --git a/include/version_specifier.h b/include/version_specifier.h new file mode 100644 index 0000000..253840b --- /dev/null +++ b/include/version_specifier.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#ifndef WLCS_VERSION_SPECIFIER_H_ +#define WLCS_VERSION_SPECIFIER_H_ + +#include +#include + +namespace wlcs +{ +class VersionSpecifier +{ +public: + VersionSpecifier() = default; + virtual ~VersionSpecifier() = default; + + virtual auto select_version(uint32_t max_version) const -> std::experimental::optional = 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 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 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 index 0000000..645b411 --- /dev/null +++ b/include/wl_handle.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + * William Wold + */ + +#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 +#include + +struct wl_proxy; + +namespace wlcs +{ +template +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::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(proxy); + } + +private: + T* const proxy; + bool owns_wl_object; +}; + +template +auto wrap_wl_object(WlType* proxy) -> WlHandle +{ + static_assert( + WlInterfaceDescriptor::has_specialisation, + "Missing specialisation for WlInterfaceDescriptor"); + return WlHandle{proxy}; +} + +} + +#endif // WLCS_WL_PROXY_H_ diff --git a/include/wl_interface_descriptor.h b/include/wl_interface_descriptor.h new file mode 100644 index 0000000..ae71a93 --- /dev/null +++ b/include/wl_interface_descriptor.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#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 +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 \ + { \ + 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 index 0000000..0bb79dd --- /dev/null +++ b/include/wlcs/display_server.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ +#ifndef WLCS_SERVER_H_ +#define WLCS_SERVER_H_ + +#include +#include + +#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 index 0000000..0ee14d3 --- /dev/null +++ b/include/wlcs/pointer.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ +#ifndef WLCS_POINTER_H_ +#define WLCS_POINTER_H_ + +#include + +#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 index 0000000..8dfe3e6 --- /dev/null +++ b/include/wlcs/touch.h @@ -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 . + * + * Authored by: William Wold + */ +#ifndef WLCS_TOUCH_H_ +#define WLCS_TOUCH_H_ + +#include + +#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 index 0000000..7d2d3c8 --- /dev/null +++ b/include/xdg_output_v1.h @@ -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 . + * + * Authored by: William Wold + */ + +#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 + +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 const impl; +}; + +class XdgOutputV1 +{ +public: + struct State + { + std::experimental::optional> logical_position; + std::experimental::optional> logical_size; + std::experimental::optional name; + std::experimental::optional 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 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 index 0000000..bd2b7e2 --- /dev/null +++ b/include/xdg_shell_stable.h @@ -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 . + * + * Authored by: William Wold + */ + +#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 notification) + { + configure_notifiers.push_back(notification); + } + + operator xdg_surface*() const {return shell_surface;} + + std::vector> configure_notifiers; + +private: + static void configure_thunk(void *data, struct xdg_surface *, uint32_t serial) + { + for (auto& notifier : static_cast(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 notification) + { + configure_notifiers.push_back(notification); + } + + void add_close_notification(std::function notification) + { + close_notifiers.push_back(notification); + } + + operator xdg_toplevel*() const {return toplevel;} + + XdgSurfaceStable* const shell_surface; + xdg_toplevel* toplevel; + + std::vector> configure_notifiers; + std::vector> 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(data)->configure_notifiers) + { + notifier(width, height, states); + } + } + + static void close_thunk(void *data, struct xdg_toplevel *) + { + for (auto& notifier : static_cast(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 parent, + XdgPositionerStable& positioner); + XdgPopupStable(XdgPopupStable const&) = delete; + XdgPopupStable& operator=(XdgPopupStable const&) = delete; + ~XdgPopupStable(); + + void add_configure_notification(std::function notification) + { + configure_notifiers.push_back(notification); + } + + void add_close_notification(std::function notification) + { + popup_done_notifiers.push_back(notification); + } + + operator xdg_popup*() const {return popup;} + + XdgSurfaceStable* const shell_surface; + xdg_popup* const popup; + + std::vector> configure_notifiers; + std::vector> 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(data)->configure_notifiers) + { + notifier(x, y, width, height); + } + } + + static void popup_done_thunk(void *data, struct xdg_popup*) + { + for (auto& notifier : static_cast(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 index 0000000..73394f2 --- /dev/null +++ b/include/xdg_shell_v6.h @@ -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 . + * + * Authored by: William Wold + */ + +#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 notification) + { + configure_notifiers.push_back(notification); + } + + operator zxdg_surface_v6*() const {return shell_surface;} + + std::vector> configure_notifiers; + +private: + static void configure_thunk(void *data, struct zxdg_surface_v6 *, uint32_t serial) + { + for (auto& notifier : static_cast(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 notification) + { + configure_notifiers.push_back(notification); + } + + void add_close_notification(std::function notification) + { + close_notifiers.push_back(notification); + } + + operator zxdg_toplevel_v6*() const {return toplevel;} + + XdgSurfaceV6* const shell_surface; + zxdg_toplevel_v6* toplevel; + + std::vector> configure_notifiers; + std::vector> 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(data)->configure_notifiers) + { + notifier(width, height, states); + } + } + + static void close_thunk(void *data, struct zxdg_toplevel_v6 *) + { + for (auto& notifier : static_cast(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 notification) + { + configure_notifiers.push_back(notification); + } + + void add_close_notification(std::function 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> configure_notifiers; + std::vector> 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(data)->configure_notifiers) + { + notifier(x, y, width, height); + } + } + + static void popup_done_thunk(void *data, struct zxdg_popup_v6 *) + { + for (auto& notifier : static_cast(data)->popup_done_notifiers) + { + notifier(); + } + } +}; + +} + +#endif // WLCS_XDG_SHELL_V6_ diff --git a/spread.yaml b/spread.yaml new file mode 100644 index 0000000..234c33f --- /dev/null +++ b/spread.yaml @@ -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 index 0000000..d5e5561 --- /dev/null +++ b/spread/build/alpine/task.yaml @@ -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 index 0000000..065c796 --- /dev/null +++ b/spread/build/fedora/task.yaml @@ -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 index 0000000..ad45894 --- /dev/null +++ b/spread/build/ubuntu/task.yaml @@ -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 index 0000000..966dc3d --- /dev/null +++ b/src/data_device.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#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(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(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(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(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(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(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(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(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(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 index 0000000..d39e9bf --- /dev/null +++ b/src/gtk_primary_selection.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "gtk_primary_selection.h" + +#include + +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(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(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(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(data)->send(source, mime_type, fd); +} + +void wlcs::GtkPrimarySelectionSourceListener::cancelled(void* data, gtk_primary_selection_source* source) +{ + if (active_listeners.includes(data)) + static_cast(data)->cancelled(source); +} diff --git a/src/helpers.cpp b/src/helpers.cpp new file mode 100644 index 0000000..7dc4f26 --- /dev/null +++ b/src/helpers.cpp @@ -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 . + * + * Authored by: + * Alexandros Frantzis + */ + +#include "helpers.h" +#include "shared_library.h" + +#include +#include + +#include +#include +#include + +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(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 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 const& entry_point) +{ + ::entry_point = entry_point; +} + +std::shared_ptr 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 index 0000000..3550cfe --- /dev/null +++ b/src/in_process_server.cpp @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + Impl( + WlcsPointer* raw_device, + std::shared_ptr const& proxy, + std::shared_ptr 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 + void setup_thunks(std::shared_ptr 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 const keep_dso_loaded; + WlcsPointer* const pointer; + + std::function move_absolute_thunk; + std::function move_relative_thunk; + std::function button_down_thunk; + std::function button_up_thunk; + std::function destroy_thunk; +}; + +wlcs::Pointer::~Pointer() = default; +wlcs::Pointer::Pointer(Pointer&&) = default; + +template +wlcs::Pointer::Pointer( + WlcsPointer* raw_device, + std::shared_ptr const& proxy, + std::shared_ptr const& keep_dso_loaded) + : impl{std::make_unique(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 + Impl( + WlcsTouch* raw_device, + std::shared_ptr const& proxy, + std::shared_ptr 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 + void set_up_thunks(std::shared_ptr 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 const keep_dso_loaded; + std::unique_ptr> const touch; + + std::function touch_down_thunk; + std::function touch_move_thunk; + std::function touch_up_thunk; +}; + +wlcs::Touch::~Touch() = default; +wlcs::Touch::Touch(Touch&&) = default; + +template +wlcs::Touch::Touch( + WlcsTouch* raw_device, + std::shared_ptr const& proxy, + std::shared_ptr const& keep_dso_loaded) + : impl{std::make_unique(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 const> extract_supported_extensions(WlcsDisplayServer* server) +{ + if (server->version < 2) + { + return {}; + } + + auto const descriptor = server->get_descriptor(server); + auto extensions = std::make_shared>(); + + 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 + auto register_op(Callable handler) + { + return handler; + } +}; +} + +class wlcs::Server::Impl +{ +public: + Impl(std::shared_ptr 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()); + } + } + + 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(), 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(), 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> supported_extensions() const + { + return supported_extensions_; + } + +private: + struct ThreadContext + { + explicit ThreadContext(std::unique_ptr loop) + : event_loop{std::move(loop)}, + proxy{std::make_shared(event_loop.get())} + { + } + ThreadContext(ThreadContext&&) = default; + + ~ThreadContext() + { + if (server_thread.joinable()) + { + server_thread.join(); + } + } + + std::unique_ptr event_loop; + std::thread server_thread; + std::shared_ptr proxy; + }; + + static std::experimental::optional 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{ + 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 const server; + std::experimental::optional thread_context; + std::shared_ptr const hooks; + std::shared_ptr const> const supported_extensions_; + + template + void initialise_thunks(std::shared_ptr 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 stop_thunk; + std::function create_client_socket_thunk; + std::function create_pointer_thunk; + std::function create_touch_thunk; + std::function position_window_absolute_thunk; +}; + +wlcs::Server::Server( + std::shared_ptr const& hooks, + int argc, + char const** argv) + : impl{std::make_unique(hooks, argc, argv)} +{ +} + +wlcs::Server::~Server() = default; + +void wlcs::Server::start() +{ + impl->start(); +} + +void wlcs::Server::stop() +{ + impl->stop(); +} + +std::shared_ptr> 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, ®istry_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 callback) + { + destruction_callbacks.push_back(callback); + } + + ShmBuffer const& create_buffer(Client& client, int width, int height) + { + auto buffer = std::make_shared(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(client, surface); + auto xdg_toplevel = std::make_shared(*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(client, surface); + auto xdg_toplevel = std::make_shared(*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 pointer_position() const + { + if (pointer_events_pending()) + BOOST_THROW_EXCEPTION(std::runtime_error("Pointer events pending")); + return current_pointer_location.value().coordinates; + }; + + std::pair 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 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 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 + * 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(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> 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(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(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(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(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> 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(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(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(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(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(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 position) + { + std::vector 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 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 position) + { + + std::vector 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> const& buttons) + { + for (auto const& button : buttons) + { + std::vector 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(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(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(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(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(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(ctx); + me->global_type_names[id] = interface; + me->globals[interface] = Global{id, version}; + + if ("wl_shm"s == interface) + { + me->shm = static_cast( + wl_registry_bind(registry, id, &wl_shm_interface, version)); + } + else if ("wl_compositor"s == interface) + { + me->compositor = static_cast( + wl_registry_bind(registry, id, &wl_compositor_interface, version)); + } + else if ("wl_subcompositor"s == interface) + { + me->subcompositor = static_cast( + wl_registry_bind(registry, id, &wl_subcompositor_interface, version)); + } + else if ("wl_shell"s == interface) + { + me->shell = static_cast( + wl_registry_bind(registry, id, &wl_shell_interface, version)); + } + else if ("wl_seat"s == interface) + { + me->seat = static_cast( + 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( + wl_registry_bind(registry, id, &wl_output_interface, version)); + auto output = std::make_unique(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( + wl_registry_bind(registry, id, &zxdg_shell_v6_interface, version)); + } + else if ("xdg_wm_base"s == interface) + { + me->xdg_shell_stable = static_cast( + 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(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 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> destruction_callbacks; + struct xdg_wm_base* xdg_shell_stable = nullptr; + + struct Global + { + uint32_t id; + uint32_t version; + }; + std::map globals; + std::map global_type_names; + + struct SurfaceLocation + { + wl_surface* surface; + std::pair coordinates; + }; + std::experimental::optional current_pointer_location; + std::experimental::optional pending_pointer_location; + bool pending_pointer_leave{false}; + std::map> pending_buttons; ///< Maps button id to the serial and if it's down + std::map current_touches; ///< Touches that have gotten a frame event + std::map pending_touches; ///< Touches that have gotten down or motion events without a frame + std::set pending_up_touches; ///< Touches that have gotten up events without a frame + + std::vector enter_notifiers; + std::vector leave_notifiers; + std::vector motion_notifiers; + std::vector 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(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 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 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 wlcs::Client::pointer_position() const +{ + return impl->pointer_position(); +} + +std::pair 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 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*>(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 const& on_frame) + { + std::unique_ptr> holder{ + new std::function(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(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 callback) + { + destruction_callbacks.push_back(callback); + } + + Client& owner() const + { + return owner_; + } +private: + + static std::vector> 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*>(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> destruction_callbacks; +}; + +std::vector> wlcs::Surface::Impl::pending_callbacks; + +constexpr wl_callback_listener wlcs::Surface::Impl::frame_listener; + +wlcs::Surface::Surface(Client& client) + : impl{std::make_unique(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 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 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(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(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 const& on_release) + { + release_notifiers.push_back(on_release); + } + +private: + static void on_release(void* ctx, wl_buffer* /*buffer*/) + { + auto me = static_cast(ctx); + + std::vectorrelease_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> release_notifiers; +}; + +constexpr wl_buffer_listener wlcs::ShmBuffer::Impl::listener; + +wlcs::ShmBuffer::ShmBuffer(Client &client, int width, int height) + : impl{std::make_unique(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 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 index 0000000..19a8ef1 --- /dev/null +++ b/src/input_method.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#include "input_method.h" +#include + +auto wlcs::InputMethod::all_input_methods() -> std::vector> +{ + return { + std::make_shared(), + std::make_shared()}; +} + +struct wlcs::PointerInputMethod::Pointer : Device +{ + Pointer(wlcs::Server& server) + : pointer{server.create_pointer()} + { + } + + void down_at(std::pair 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 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 +{ + return std::make_unique(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 +{ + 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 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 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 +{ + return std::make_unique(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 +{ + 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 const& param) +{ + return out << param->name; +} diff --git a/src/layer_shell_v1.cpp b/src/layer_shell_v1.cpp new file mode 100644 index 0000000..814a691 --- /dev/null +++ b/src/layer_shell_v1.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#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(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(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, ¤t_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 index 0000000..b77c79b --- /dev/null +++ b/src/main.cpp @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#include +#include +#include + +#include + +#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(argv)); + + std::shared_ptr dso; + try + { + dso = std::make_shared(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 entry_point; + try + { + entry_point = std::shared_ptr{ + dso, + dso->load_function("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 index 0000000..623bc1c --- /dev/null +++ b/src/pointer_constraints_unstable_v1.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "pointer_constraints_unstable_v1.h" + +#include "in_process_server.h" +#include + +wlcs::ZwpPointerConstraintsV1::ZwpPointerConstraintsV1(Client& client) : + manager{client.bind_if_supported(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(self)->confined(); }, + [](void* self, auto*) { static_cast(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(self)->locked(); }, + [](void* self, auto*) { static_cast(self)->unlocked(); }, + }; diff --git a/src/primary_selection.cpp b/src/primary_selection.cpp new file mode 100644 index 0000000..85bb0c1 --- /dev/null +++ b/src/primary_selection.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "primary_selection.h" + +#include + +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(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(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(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(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(data)->cancelled(source); +} diff --git a/src/protocol/gtk-primary-selection.xml b/src/protocol/gtk-primary-selection.xml new file mode 100644 index 0000000..aee85c2 --- /dev/null +++ b/src/protocol/gtk-primary-selection.xml @@ -0,0 +1,225 @@ + + + + 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. + + + + 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. + + + + + 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. + + + + + Create a new primary selection source. + + + + + + + Create a new data device for a given seat. + + + + + + + + Destroy the primary selection device manager. + + + + + + + + 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. + + + + + + + + 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. + + + + + + + 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. + + + + + + + Destroy the primary selection device. + + + + + + + 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. + + + + + 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. + + + + + + + + Destroy the primary selection offer. + + + + + + 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. + + + + + + + + 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. + + + + + This request adds a mime type to the set of mime types advertised to + targets. Can be called several times to offer multiple types. + + + + + + + Destroy the primary selection source. + + + + + + Request for the current primary selection contents from the client. + Send the specified mime type over the passed file descriptor, then + close it. + + + + + + + + This primary selection source is no longer valid. The client should + clean up and destroy this primary selection source. + + + + \ 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 index 0000000..4e67a13 --- /dev/null +++ b/src/protocol/pointer-constraints-unstable-v1.xml @@ -0,0 +1,339 @@ + + + + + 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. + + + + 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. + + + + + 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. + + + + + These errors can be emitted in response to wp_pointer_constraints + requests. + + + + + + + These values represent different lifetime semantics. They are passed + as arguments to the factory requests to specify how the constraint + lifetimes should be managed. + + + + 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. + + + + + 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. + + + + + + + Used by the client to notify the server that it will no longer use this + pointer constraints object. + + + + + + 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. + + + + + + + + + + + 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. + + + + + + + + + + + + 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. + + + + + Destroy the locked pointer object. If applicable, the compositor will + unlock the pointer. + + + + + + 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. + + + + + + + + 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. + + + + + + + Notification that the pointer lock of the seat's pointer is activated. + + + + + + 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. + + + + + + + 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. + + + + + Destroy the confined pointer object. If applicable, the compositor will + unconfine the pointer. + + + + + + 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. + + + + + + + Notification that the pointer confinement of the seat's pointer is + activated. + + + + + + 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. + + + + + diff --git a/src/protocol/primary-selection-unstable-v1.xml b/src/protocol/primary-selection-unstable-v1.xml new file mode 100644 index 0000000..e5a39e3 --- /dev/null +++ b/src/protocol/primary-selection-unstable-v1.xml @@ -0,0 +1,225 @@ + + + + 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. + + + + 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. + + + + + 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. + + + + + Create a new primary selection source. + + + + + + + Create a new data device for a given seat. + + + + + + + + Destroy the primary selection device manager. + + + + + + + + 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. + + + + + + + + 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. + + + + + + + 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. + + + + + + + Destroy the primary selection device. + + + + + + + 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. + + + + + 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. + + + + + + + + Destroy the primary selection offer. + + + + + + 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. + + + + + + + + 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. + + + + + This request adds a mime type to the set of mime types advertised to + targets. Can be called several times to offer multiple types. + + + + + + + Destroy the primary selection source. + + + + + + Request for the current primary selection contents from the client. + Send the specified mime type over the passed file descriptor, then + close it. + + + + + + + + This primary selection source is no longer valid. The client should + clean up and destroy this primary selection source. + + + + diff --git a/src/protocol/relative-pointer-unstable-v1.xml b/src/protocol/relative-pointer-unstable-v1.xml new file mode 100644 index 0000000..ca6f81d --- /dev/null +++ b/src/protocol/relative-pointer-unstable-v1.xml @@ -0,0 +1,136 @@ + + + + + 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. + + + + 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. + + + + + A global interface used for getting the relative pointer object for a + given pointer. + + + + + Used by the client to notify the server that it will no longer use this + relative pointer manager object. + + + + + + Create a relative pointer interface given a wl_pointer object. See the + wp_relative_pointer interface for more details. + + + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + + diff --git a/src/protocol/wayland.xml b/src/protocol/wayland.xml new file mode 100644 index 0000000..29b63be --- /dev/null +++ b/src/protocol/wayland.xml @@ -0,0 +1,2746 @@ + + + + + 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. + + + + + The core global object. This is a special singleton object. It + is used for internal Wayland protocol features. + + + + + 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. + + + + + + + This request creates a registry object that allows the client + to list and bind the global objects available from the + compositor. + + + + + + + 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. + + + + + + + + + These errors are global and can be emitted in response to any + server request. + + + + + + + + + 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. + + + + + + + + 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. + + + + + Binds a new, client-created object to the server using the + specified name as the identifier. + + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + Clients can handle the 'done' event to get notified when + the related request is done. + + + + + Notify the client when the related request is done. + + + + + + + + A compositor. This object is a singleton global. The + compositor is in charge of combining the contents of multiple + surfaces into one displayable output. + + + + + Ask the compositor to create a new surface. + + + + + + + Ask the compositor to create a new region. + + + + + + + + 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. + + + + + 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. + + + + + + + + + + + + Destroy the shared memory pool. + + The mmapped memory will be released when all + buffers that have been created from this pool + are gone. + + + + + + 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. + + + + + + + + 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. + + + + + These errors can be emitted in response to wl_shm requests. + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + + + + + + + + + Informs the client about a valid pixel format that + can be used for buffers. Known formats include + argb8888 and xrgb8888. + + + + + + + + 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. + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + + + + + + + + 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. + + + + + + + + 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. + + + + + + + + Destroy the data offer. + + + + + + Sent immediately after creating the wl_data_offer object. One + event per offered mime type. + + + + + + + + + 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. + + + + + + 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. + + + + + + + + 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. + + + + + + + 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. + + + + + + + + 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. + + + + + + + + + + This request adds a mime type to the set of mime types + advertised to targets. Can be called several times to offer + multiple types. + + + + + + + Destroy the data source. + + + + + + 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. + + + + + + + Request for data from the client. Send the data as the + specified mime type over the passed file descriptor, then + close it. + + + + + + + + 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. + + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + 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. + + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + + 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. + + + + + + + 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. + + + + + + + + + + + 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. + + + + + + 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. + + + + + + + + + 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. + + + + + + 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. + + + + + + + + + This request destroys the data device. + + + + + + + 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. + + + + + Create a new data source. + + + + + + + Create a new data device for a given seat. + + + + + + + + + + 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"). + + + + + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + 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. + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. + + + + + + + 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). + + + + + + + + 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. + + + + + + + + + + + + + + + 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). + + + + + + + + + Map the surface as a toplevel surface. + + A toplevel surface is not fullscreen, maximized or transient. + + + + + + These flags specify details of the expected behaviour + of transient surfaces. Used in the set_transient request. + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + Ping a client to check if it is receiving events and sending + requests. A client is expected to reply with a pong request. + + + + + + + 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. + + + + + + + + + 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. + + + + + + + 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). + + + + + These errors can be emitted in response to wl_surface requests. + + + + + + + + Deletes the surface and invalidates its object ID. + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + 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. + + + + + + + + + + + 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. + + + + + This is a bitmask of capabilities this seat has; if a member is + set, then it is present on the seat. + + + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + Using this request a client can tell the server that it is not going to + use the seat object anymore. + + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + + Notification of pointer location change. The arguments + surface_x and surface_y are the location relative to the + focused surface. + + + + + + + + + Describes the physical state of a button that produced the 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. + + + + + + + + + + Describes the axis types of scroll events. + + + + + + + + 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. + + + + + + + + + + + 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. + + + + + + + + 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. + + + + + + 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. + + + + + + + + + + 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. + + + + + + + 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. + + + + + + + + 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. + + + + + + + + + The wl_keyboard interface represents one or more keyboards + associated with a seat. + + + + + This specifies the format of the keymap provided to the + client with the wl_keyboard.keymap event. + + + + + + + + This event provides a file descriptor to the client which can be + memory-mapped to provide a keyboard mapping description. + + + + + + + + + Notification that this seat's keyboard focus is on a certain + surface. + + + + + + + + + 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. + + + + + + + + Describes the physical state of a key that produced the key event. + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond + granularity, with an undefined base. + + + + + + + + + + Notifies clients that the modifier and/or group state has + changed, and it should update its local state. + + + + + + + + + + + + + + + + + + + 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. + + + + + + + + + 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. + + + + + 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. + + + + + + + + + + + + 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. + + + + + + + + + A touch point has changed coordinates. + + + + + + + + + + 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. + + + + + + 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. + + + + + + + + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + 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. + + + + + This enumeration describes how the physical + pixels on an output are laid out. + + + + + + + + + + + + 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. + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + These flags describe properties of an output mode. + They are used in the flags bitfield of the mode event. + + + + + + + + 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. + + + + + + + + + + + + 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. + + + + + + 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. + + + + + + + + + Using this request a client can tell the server that it is not going to + use the output object anymore. + + + + + + + A region object describes an area. + + Region objects are used to describe the opaque and input + regions of a surface. + + + + + Destroy the region. This will invalidate the object ID. + + + + + + Add the specified rectangle to the region. + + + + + + + + + + Subtract the specified rectangle from the region. + + + + + + + + + + + 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. + + + + + 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. + + + + + + + + + + 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. + + + + + + + + + + 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. + + + + + 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. + + + + + + + + + + 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. + + + + + + + + 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. + + + + + + + The sub-surface is placed just below the reference surface. + See wl_subsurface.place_above. + + + + + + + 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. + + + + + + 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. + + + + + 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 index 0000000..a97738f --- /dev/null +++ b/src/protocol/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,259 @@ + + + + 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. + + + + + 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 + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + 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. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + 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. + + + + + + + + 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. + + + + + + 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. + + + + + + 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. + + + + + + + + + + + + + + + + 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. + + + + + + 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. + + + + + + + + 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. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + diff --git a/src/protocol/wlr-layer-shell-unstable-v1.xml b/src/protocol/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..6a5d5d3 --- /dev/null +++ b/src/protocol/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,285 @@ + + + + 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. + + + + + 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. + + + + + 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. + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + 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. + + + + + 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. + + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + This request destroys the layer surface. + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + + + + + + + + diff --git a/src/protocol/xdg-output-unstable-v1.xml b/src/protocol/xdg-output-unstable-v1.xml new file mode 100644 index 0000000..fe3a70a --- /dev/null +++ b/src/protocol/xdg-output-unstable-v1.xml @@ -0,0 +1,220 @@ + + + + + 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. + + + + 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. + + + + + A global factory interface for xdg_output objects. + + + + + 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. + + + + + + This creates a new xdg_output object for the given wl_output. + + + + + + + + + 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. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output object anymore. + + + + + + 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. + + + + + + + + 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). + + + + + + + + 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. + + + + + + + + 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. + + + + + + + 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. + + + + + + diff --git a/src/protocol/xdg-shell-unstable-v6.xml b/src/protocol/xdg-shell-unstable-v6.xml new file mode 100644 index 0000000..1c0f924 --- /dev/null +++ b/src/protocol/xdg-shell-unstable-v6.xml @@ -0,0 +1,1044 @@ + + + + + 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. + + + + + 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. + + + + + + + + + + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_shell.ping. + + + + + + + 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. + + + + + + + + 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. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + 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. + + + + + + + + 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. + + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + + 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. + + + + + + + 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. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of a monitor. + + + + + 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. + + + + + 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. + + + + + 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. + + + + + 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. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + 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. + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed. + + + + + + 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. + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + 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. + + + + + + + + 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. + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + + + 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/ + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + 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. + + + + + + + + + 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. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client. + + + + + The surface is fullscreen. The window geometry specified in the configure + event must be obeyed by the client. + + + + + 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. + + + + + 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. + + + + + + + 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. + + + + + + + + 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. + + + + + + + + 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. + + + + + + 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. + + + + + + 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. + + + + + + + + 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. + + + + + + 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. + + + + + + + + + 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. + + + + + + + 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. + + + + + + + + + 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. + + + + + + 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. + + + + + + + + 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. + + + + + + + + + + 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. + + + + + diff --git a/src/protocol/xdg-shell.xml b/src/protocol/xdg-shell.xml new file mode 100644 index 0000000..d524ea9 --- /dev/null +++ b/src/protocol/xdg-shell.xml @@ -0,0 +1,1120 @@ + + + + + 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. + + + + + 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. + + + + + + + + + + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_wm_base.ping. + + + + + + + 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. + + + + + + + + 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. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + 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. + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + 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. + + + + + + + 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. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of an output. + + + + + 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. + + + + + 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. + + + + + 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. + + + + + 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. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + 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. + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed. + + + + + + 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. + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + 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. + + + + + + + + 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. + + + + + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. + + + + + + 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. + + + + + + + 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. + + + + + + + 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/ + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + 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. + + + + + + + + + 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. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client. + + + + + 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. + + + + + 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. + + + + + 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. + + + + + + + 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. + + + + + + + + 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. + + + + + + + + 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. + + + + + + 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. + + + + + + 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. + + + + + + + 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). + + + + + + 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. + + + + + + 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. + + + + + + + + + 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. + + + + + + + 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. + + + + + + + + + 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. + + + + + + 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. + + + + + + + + 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. + + + + + + + + + + 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. + + + + + diff --git a/src/relative_pointer_unstable_v1.cpp b/src/relative_pointer_unstable_v1.cpp new file mode 100644 index 0000000..deb438c --- /dev/null +++ b/src/relative_pointer_unstable_v1.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "relative_pointer_unstable_v1.h" + +#include "in_process_server.h" +#include + +wlcs::ZwpRelativePointerManagerV1::ZwpRelativePointerManagerV1(Client& client) : + manager{client.bind_if_supported(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(self)->relative_motion(args...); } + }; diff --git a/src/shared_library.cpp b/src/shared_library.cpp new file mode 100644 index 0000000..8fa7e26 --- /dev/null +++ b/src/shared_library.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "shared_library.h" + +#include +#include + +#include + +#include + +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 index 0000000..6f32cff --- /dev/null +++ b/src/surface_builder.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#include "surface_builder.h" +#include "xdg_shell_stable.h" + +auto wlcs::SurfaceBuilder::all_surface_types() -> std::vector> +{ + return { + std::make_shared(), + std::make_shared(), + std::make_shared(0, 0, 0, 0), + std::make_shared(12, 5, 20, 6), + std::make_shared(std::make_pair(0, 0)), + std::make_shared(std::make_pair(7, 12))}; +} + +auto wlcs::SurfaceBuilder::toplevel_surface_types() -> std::vector> +{ + return { + std::make_shared(), + std::make_shared(), + std::make_shared(0, 0, 0, 0)}; +} + +auto wlcs::WlShellSurfaceBuilder::build( + wlcs::Server& server, + wlcs::Client& client, + std::pair position, + std::pair size) const -> std::unique_ptr +{ + auto surface = std::make_unique( + 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 position, + std::pair size) const -> std::unique_ptr +{ + auto surface = std::make_unique( + 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 position, + std::pair size) const -> std::unique_ptr +{ + auto surface = std::make_unique(client); + auto xdg_surface = std::make_shared(client, *surface); + auto xdg_toplevel = std::make_shared(*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 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 position, + std::pair size) const -> std::unique_ptr +{ + auto main_surface = std::make_shared( + 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::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 const& param) +{ + return out << param->name; +} diff --git a/src/termcolor.hpp b/src/termcolor.hpp new file mode 100644 index 0000000..f8a8629 --- /dev/null +++ b/src/termcolor.hpp @@ -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 +#elif defined(TERMCOLOR_OS_WINDOWS) +# include +# include +#endif + + +#include +#include + + + +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(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(foreground); + } + + if (background != -1) + { + info.wAttributes &= ~(info.wAttributes & 0xF0); + info.wAttributes |= static_cast(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 index 0000000..a0bab36 --- /dev/null +++ b/src/test_c_compile.c @@ -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 index 0000000..93e678c --- /dev/null +++ b/src/thread_proxy.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#ifndef WLCS_THREAD_PROXY_H_ +#define WLCS_THREAD_PROXY_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 +struct callable_traits : public callable_traits {}; + +template +struct callable_traits +{ + using return_type = Returns; + using args = typename std::tuple; +}; + +/* + * 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 +Returns call_helper(Callable const& func, std::tuple const& args, std::index_sequence) +{ + return func(std::get(args)...); +} + +template +Returns call(Callable const& func, std::tuple const& args) +{ + return call_helper(func, args, std::index_sequence_for{}); +} + +/* + * tuple_from_buffer base case: To unpack a buffer containing no values, + * return a std::tuple<>. + */ +template< + typename... Args, + typename std::enable_if::type = 0> +std::tuple tuple_from_buffer(char*, std::tuple const*) +{ + return std::tuple{}; +} + +/* + * Unpack a linear buffer into a std::tuple of its component types. + */ +template +std::tuple tuple_from_buffer(char* buffer, std::tuple 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 const* type_deducer = nullptr; + return std::tuple_cat(std::make_tuple(aligned_arg), tuple_from_buffer(buffer + sizeof(Head), type_deducer)); +} + +std::array setup_socketpair() +{ + std::array 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 +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 +void pack_buffer(char* buffer, T&& arg, Remainder&& ...args) +{ + memcpy(buffer, &arg, sizeof(arg)); + pack_buffer(buffer + sizeof(arg), std::forward(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 +struct conjunction : std::true_type {}; + +template +struct conjunction : Cond {}; + +template +struct conjunction : + std::conditional_t, Head> {}; + +template +constexpr bool all_args_are_trivially_copyable(std::tuple const*) +{ + return conjunction...>::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 +{ +private: + + template + 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(buffer) = opcode; + pack_buffer(buffer + sizeof(uint32_t), std::forward(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 + 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(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 + auto make_send_functor(uint32_t opcode, std::tuple 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 lock{me->message_serialiser}; + me->send_message(opcode, std::forward(args)...); + me->wait_for_reply(); + }; + } + + template + auto make_send_functor(uint32_t opcode, std::tuple 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 lock{me->message_serialiser}; + me->send_message(opcode, std::forward(args)...); + return me->wait_for_reply(); + }; + } + + template< + typename Callable, + typename... Args, + typename + std::enable_if_t< + std::is_same< + typename callable_traits::type>::return_type, + void>::value, + int> = 0> + auto make_recv_functor(Callable handler, std::tuple const*) + { + return + [this, handler = std::move(handler)](void* data) + { + std::tuple const* type_resolver = nullptr; + auto const args = tuple_from_buffer(static_cast(data), type_resolver); + + call(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::type>::return_type, + void>::value, int> = 0> + auto make_recv_functor(Callable handler, std::tuple const*) + { + return + [this, handler = std::move(handler)](void* data) + { + using traits = callable_traits::type>; + + std::tuple const* type_resolver = nullptr; + auto const args = tuple_from_buffer(static_cast(data), type_resolver); + + auto const val = call(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::type>::return_type, + void>::value, int> = 0> + auto register_op(Callable handler) + { + using traits = callable_traits::type>; + // This is technically too restrictive; std::tuple 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*; 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::type>::return_type, + void>::value, int> = 0> + auto register_op(Callable handler) + { + using traits = callable_traits::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; 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); + } + + 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(data); + char buffer[max_message_size]; + + recv(fd, buffer, sizeof(buffer), 0); + + auto const opcode = *reinterpret_cast(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 const fds; + struct wl_event_source* const source; + std::mutex mutable message_serialiser; + std::vector> handlers; + std::function const destructor; +}; +} + + +#endif //WLCS_THREAD_PROXY_H_ diff --git a/src/version_specifier.cpp b/src/version_specifier.cpp new file mode 100644 index 0000000..3009ed5 --- /dev/null +++ b/src/version_specifier.cpp @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#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 +{ + 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 +{ + 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 index 0000000..a92e469 --- /dev/null +++ b/src/xdg_output_v1.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#include "xdg_output_v1.h" +#include "wl_handle.h" +#include "version_specifier.h" +#include + +struct wlcs::XdgOutputManagerV1::Impl +{ + Impl(Client& client) + : client{client}, + manager{client.bind_if_supported(AnyVersion)} + { + } + + Client& client; + WlHandle const manager; +}; + +wlcs::XdgOutputManagerV1::XdgOutputManagerV1(Client& client) + : impl{std::make_unique(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(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(data); + impl->_state.logical_size = std::make_pair(width, height); + impl->dirty = true; + }, + + [] /* done */ (void *data, zxdg_output_v1 *) + { + auto const impl = static_cast(data); + if (impl->version < 3) + impl->dirty = false; + }, + + [] /* name */ (void *data, zxdg_output_v1 *, const char *name) + { + auto const impl = static_cast(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(data); + impl->_state.description = std::string{description}; + impl->dirty = true; + }, +}; + +wlcs::XdgOutputV1::XdgOutputV1(XdgOutputManagerV1& manager, size_t output_index) + : impl{std::make_shared(manager, output_index)} +{ + if (impl->version >= 3) + { + manager.client().add_output_done_notifier(output_index, [weak = std::weak_ptr(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 index 0000000..a9b66f3 --- /dev/null +++ b/src/xdg_shell_stable.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#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(states->data); + (char*)item < static_cast(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 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 index 0000000..adf9b19 --- /dev/null +++ b/src/xdg_shell_v6.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#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(states->data); + (char*)item < static_cast(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 index 0000000..77e1a64 --- /dev/null +++ b/src/xfail_supporting_test_listener.cpp @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#include "xfail_supporting_test_listener.h" +#include +#include +#include "termcolor.hpp" + +testing::XFailSupportingTestListenerWrapper::XFailSupportingTestListenerWrapper(std::unique_ptr&& 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{}; + } + 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(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 index 0000000..929ebe9 --- /dev/null +++ b/src/xfail_supporting_test_listener.h @@ -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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#ifndef WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_ +#define WLCS_XFAIL_SUPPORTING_TEST_LISTENER_H_ + +#include + +#include +#include +#include +#include +#include + +namespace testing +{ +class XFailSupportingTestListenerWrapper : public testing::TestEventListener +{ +public: + explicit XFailSupportingTestListenerWrapper(std::unique_ptr&& 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 const delegate; + + std::chrono::steady_clock::time_point current_test_start; + ::testing::TestInfo const* current_test_info; + std::experimental::optional> current_skip_reasons; + + std::unordered_set failed_test_names; + std::unordered_set 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 index 0000000..7e15792 --- /dev/null +++ b/tests/copy_cut_paste.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "data_device.h" +#include "helpers.h" +#include "in_process_server.h" +#include "wl_handle.h" +#include "version_specifier.h" + +#include + +#include + +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 const manager{ + this->bind_if_supported(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 const manager{ + this->bind_if_supported(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 index 0000000..e938b8e --- /dev/null +++ b/tests/frame_submission.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "helpers.h" +#include "in_process_server.h" + +#include + +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(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 index 0000000..2dbb5e9 --- /dev/null +++ b/tests/gtk_primary_selection.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "gtk_primary_selection.h" + +#include "in_process_server.h" +#include "version_specifier.h" + +#include +#include + +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 manager{ + this->bind_if_supported(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 const manager{ + this->bind_if_supported(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 index 0000000..a1cf93f --- /dev/null +++ b/tests/pointer_constraints.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "pointer_constraints_unstable_v1.h" +#include "helpers.h" +#include "in_process_server.h" + +#include + +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 index 0000000..720ab3f --- /dev/null +++ b/tests/primary_selection.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "primary_selection.h" + +#include "in_process_server.h" +#include "version_specifier.h" + +#include +#include + +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 const manager{ + this->bind_if_supported(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 const manager{ + this->bind_if_supported(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 index 0000000..b2af06a --- /dev/null +++ b/tests/relative_pointer.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "relative_pointer_unstable_v1.h" +#include "helpers.h" +#include "in_process_server.h" + +#include + +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 index 0000000..e5428e8 --- /dev/null +++ b/tests/self_test.cpp @@ -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 . + * + * Authored by: Alan Griffiths + */ + +#include "data_device.h" +#include "helpers.h" +#include "gtest_helpers.h" +#include "in_process_server.h" +#include "version_specifier.h" + +#include + +#include + +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 index 0000000..2343e11 --- /dev/null +++ b/tests/subsurfaces.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#include "helpers.h" +#include "in_process_server.h" +#include "xdg_shell_v6.h" + +#include + +#include + +using namespace testing; + +struct AbstractInputDevice +{ + virtual void to_screen_position(int x, int y) = 0; + virtual wl_surface* focused_window() = 0; + virtual std::pair 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 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 position_on_window() override + { + return client.touch_position(); + } + + wlcs::Client& client; + wlcs::Touch touch; +}; + +struct SubsurfaceTestParams +{ + std::string name; + std::function(wlcs::InProcessServer& server, + wlcs::Client& client, + int x, int y, + int width, int height)> make_surface; + std::function(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 +{ +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 input_device; +}; + +class SubsurfaceMultilevelTest : + public wlcs::StartedInProcessServer, + public testing::WithParamInterface +{ +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 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 + { + auto surface = client.create_wl_shell_surface( + width, + height); + server.the_server().move_surface_to(surface, x, y); + return std::make_unique(std::move(surface)); + }, + [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr + { + return std::make_unique(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 + { + auto surface = client.create_xdg_shell_v6_surface( + width, + height); + server.the_server().move_surface_to(surface, x, y); + return std::make_unique(std::move(surface)); + }, + [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr + { + return std::make_unique(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 + { + auto surface = client.create_xdg_shell_stable_surface( + width, + height); + server.the_server().move_surface_to(surface, x, y); + return std::make_unique(std::move(surface)); + }, + [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr + { + return std::make_unique(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 + { + auto surface = client.create_xdg_shell_v6_surface( + width, + height); + server.the_server().move_surface_to(surface, x, y); + return std::make_unique(std::move(surface)); + }, + [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr + { + return std::make_unique(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 + { + auto surface = client.create_wl_shell_surface( + width, + height); + server.the_server().move_surface_to(surface, x, y); + return std::make_unique(std::move(surface)); + }, + [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr + { + return std::make_unique(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 + { + auto surface = client.create_xdg_shell_v6_surface( + width, + height); + server.the_server().move_surface_to(surface, x, y); + return std::make_unique(std::move(surface)); + }, + [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr + { + return std::make_unique(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 + { + auto surface = client.create_xdg_shell_stable_surface( + width, + height); + server.the_server().move_surface_to(surface, x, y); + return std::make_unique(std::move(surface)); + }, + [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr + { + return std::make_unique(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 + { + auto surface = client.create_xdg_shell_v6_surface( + width, + height); + server.the_server().move_surface_to(surface, x, y); + return std::make_unique(std::move(surface)); + }, + [](wlcs::Server& server, wlcs::Client& client) -> std::unique_ptr + { + return std::make_unique(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 index 0000000..ce76052 --- /dev/null +++ b/tests/surface_input_regions.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#include "helpers.h" +#include "in_process_server.h" +#include "surface_builder.h" +#include "input_method.h" + +#include + +#include +#include +#include + +using namespace testing; + +enum class RegionAction +{ + add, + subtract, +}; + +struct Region +{ + struct Element + { + RegionAction action; + std::pair top_left; + std::pair size; + }; + + std::string name; + std::pair surface_size; + std::vector 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 on_surface, + std::pair 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 on_surface; + std::pair 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(std::make_shared(0, 0, 0, 0))); + +auto const all_input_types = ValuesIn(wlcs::InputMethod::all_input_methods()); + +class RegionSurfaceInputCombinations : + public wlcs::InProcessServer, + public testing::WithParamInterface, + std::shared_ptr>> +{ +}; + +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 builder; + std::shared_ptr 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 builder; + std::shared_ptr 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::shared_ptr>> +{ +}; + +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 builder; + std::shared_ptr 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 builder; + std::shared_ptr 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 builder; + std::shared_ptr 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 builder; + std::shared_ptr 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::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 builder; + std::shared_ptr 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::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 builder; + std::shared_ptr 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::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 builder; + std::shared_ptr 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::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 builder; + std::shared_ptr 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 builder; + std::shared_ptr 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::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 builder; + std::shared_ptr 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 builder; + std::shared_ptr 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::shared_ptr>> +{ +}; + +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 builder; + std::shared_ptr 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 index 0000000..284a694 --- /dev/null +++ b/tests/test_bad_buffer.cpp @@ -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 +#include + +#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 index 0000000..4eb8fc7 --- /dev/null +++ b/tests/test_surface_events.cpp @@ -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 +#include + +#include + +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 +{ +}; + +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(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(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(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> 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 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 buffers = {{ + wlcs::ShmBuffer{client, 100, 100}, + wlcs::ShmBuffer{client, 100, 100}, + wlcs::ShmBuffer{client, 100, 100} + }}; + std::array 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 index 0000000..ab4c499 --- /dev/null +++ b/tests/touches.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#include "helpers.h" +#include "in_process_server.h" +#include "xdg_shell_stable.h" +#include "surface_builder.h" + +#include + +#include +#include + +using namespace testing; + +class TouchTest: + public wlcs::InProcessServer, + public testing::WithParamInterface> +{ +}; + +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(*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(*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(*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 index 0000000..9b0fb3b --- /dev/null +++ b/tests/wl_output.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#include "helpers.h" +#include "in_process_server.h" +#include "version_specifier.h" + +#include + +#include + +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(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 index 0000000..25ab5f9 --- /dev/null +++ b/tests/wlr_foreign_toplevel_management_v1.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#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 +#include +#include + +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 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 const handle; + +private: + static auto get_self(void* data) -> ForeignToplevelHandle* + { + return static_cast(data); + } + + bool dirty_{false}; + std::experimental::optional title_; + std::experimental::optional app_id_; + std::vector 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(state->data); + (char*)item < static_cast(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> const& + { + toplevels_.erase( + std::remove_if( + toplevels_.begin(), + toplevels_.end(), + [](auto const& toplevel) { return toplevel->destroyed(); }), + toplevels_.end()); + + return toplevels_; + } + + wlcs::WlHandle const manager; + +private: + std::vector> toplevels_; +}; + +ForeignToplevelManager::ForeignToplevelManager(wlcs::Client& client) + : manager{client.bind_if_supported(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(data); + auto handle = std::make_unique(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 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 index 0000000..3a7c880 --- /dev/null +++ b/tests/wlr_layer_shell_v1.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#include "helpers.h" +#include "in_process_server.h" +#include "layer_shell_v1.h" +#include "xdg_shell_stable.h" + +#include + +using namespace testing; + +using Vec2 = std::pair; +using Rect = std::pair; + +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 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 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 + struct Sides + { + T left, right, top, bottom; + }; + + static auto get_all() -> std::vector + { + bool const range[]{false, true}; + std::vector result; + for (auto left: range) + { + for (auto right: range) + { + for (auto top: range) + { + for (auto bottom: range) + { + result.emplace_back(Sides{left, right, top, bottom}); + result.emplace_back(Sides{left, right, top, bottom}, Sides{6, 9, 12, 15}); + } + } + } + } + return result; + } + + LayerSurfaceLayout(Sides anchor) + : anchor{anchor}, + margin{0, 0, 0, 0} + { + } + + LayerSurfaceLayout(Sides anchor, Sides 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 const anchor; + Sides const margin; +}; + +static void invoke_zwlr_layer_surface_v1_set_margin( + zwlr_layer_surface_v1* layer_surface, + LayerSurfaceLayout::Sides 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 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 +{ +}; + +struct LayerLayerParams +{ + std::experimental::optional below; + std::experimental::optional 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 +{ +public: + struct SurfaceOnLayer + { + SurfaceOnLayer( + wlcs::Client& client, + std::experimental::optional 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 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 +{ +}; + +} + +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 index 0000000..9b1a187 --- /dev/null +++ b/tests/xdg_output_v1.cpp @@ -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 . + * + * Authored by: William Wold + */ + +#include "in_process_server.h" +#include "xdg_output_v1.h" + +#include + +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 index 0000000..d7b7ab5 --- /dev/null +++ b/tests/xdg_popup.cpp @@ -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 + +#include + +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 popup_size; // will default to XdgPopupStableTestBase::popup_(width|height) if nullopt + std::pair, std::pair> anchor_rect; // will default to the full window rect + std::experimental::optional anchor_stable; + std::experimental::optional gravity_stable; + std::experimental::optional constraint_adjustment_stable; + std::experimental::optional> 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 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> = 0; + + virtual void dispatch_until_popup_configure() = 0; + + wlcs::Server& the_server; + + wlcs::Client client; + wlcs::Surface surface; + std::experimental::optional popup_surface; + + std::experimental::optional 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, ¤t_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> override + { + return std::make_pair(state.value().x, state.value().y); + } + + wlcs::XdgSurfaceStable xdg_shell_surface; + wlcs::XdgToplevelStable toplevel; + + std::experimental::optional popup_xdg_surface; + std::experimental::optional 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, ¤t_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> override + { + return std::make_pair(state.value().x, state.value().y); + } + + wlcs::XdgSurfaceV6 xdg_shell_surface; + wlcs::XdgToplevelV6 toplevel; + + std::experimental::optional popup_xdg_surface; + std::experimental::optional 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, ¤t_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> override + { + return std::make_pair(state.value().x, state.value().y); + } + + wlcs::LayerSurfaceV1 layer_surface; + + std::experimental::optional popup_xdg_surface; + std::experimental::optional popup; + + int popup_surface_configure_count{0}; +}; + +} + +class XdgPopupPositionerTest: + public wlcs::StartedInProcessServer, + public testing::WithParamInterface +{ +}; + +TEST_P(XdgPopupPositionerTest, xdg_shell_stable_popup_placed_correctly) +{ + auto manager = std::make_unique(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(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(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(wlcs::InProcessServer* const)> build; +}; + +std::ostream& operator<<(std::ostream& out, XdgPopupTestParam const&) { return out; } + +class XdgPopupTest: + public wlcs::StartedInProcessServer, + public testing::WithParamInterface +{ +}; + +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(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(server); + }})); + +INSTANTIATE_TEST_SUITE_P( + XdgPopupUnstableV6, + XdgPopupTest, + testing::Values(XdgPopupTestParam{ + [](wlcs::InProcessServer* const server) + { + return std::make_unique(server); + }})); + +INSTANTIATE_TEST_SUITE_P( + LayerShellPopup, + XdgPopupTest, + testing::Values(XdgPopupTestParam{ + [](wlcs::InProcessServer* const server) + { + return std::make_unique(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 index 0000000..d6ea0cf --- /dev/null +++ b/tests/xdg_surface_stable.cpp @@ -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 + +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(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(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(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(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 index 0000000..0821959 --- /dev/null +++ b/tests/xdg_surface_v6.cpp @@ -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 + +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 index 0000000..837eae8 --- /dev/null +++ b/tests/xdg_toplevel_stable.cpp @@ -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 + +#include + +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, ¤t_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(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(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 index 0000000..1e69a39 --- /dev/null +++ b/tests/xdg_toplevel_v6.cpp @@ -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 + +#include + +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, ¤t_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(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(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 index 0000000..92c0c4e --- /dev/null +++ b/tools/make_release_tarball @@ -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 index 0000000..baaebe1 --- /dev/null +++ b/wlcs.pc.in @@ -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} -- 2.30.2